目录

  • 1. 基础 widget
    • 1.1 Text
      • Text各种参数
      • 多种效果合体
      • 实例:俩花活(艺术字)
    • 1.2 Row, Column
      • row示例
      • 主轴
      • 调整大小
      • Expanded
      • SizedBox
      • Spacer
      • Icon
      • Image
      • 实例:小黑子界面
    • 1.3 Container
      • 旋转
      • BoxDecoration
    • 1.4 GridView
      • 实例:ikun 相册
    • 1.5 ListView
    • 1.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的属性就可以在其生命周期内改变。那怎么区分有状态和无状态?有状态一般有两个类:

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

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

1.1 Text

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

Text各种参数

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

import 'package:flutter/material.dart';void main() {// runApp(const MyApp());// 变量const String _abc = "Clark";// Text1runApp(Text(// 文本; 文本加入变量'Hello, world! \n Hello, $_abc!',// 位置:left, right, center, justify, start, endtextAlign: 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() {// Text2runApp(// 注意:RichText一定要一个textDirectionRichText(textDirection: TextDirection.ltr,textAlign: TextAlign.center,text: TextSpan(children: <TextSpan>[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一个细一点的白字。

// Text3runApp(Stack(alignment: Alignment.center,children: <Widget>[// 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里的蒙板,这里面有三种方法:LinearGradientRadialGradientSweepGradient。第一种是线性变化,顺着一个方向渐变颜色,可以横着也可以斜着;第二种是辐射式的变化,有点像中间淡色外边深色的那种;第三种是通过设置开始角度和结束角度,设置渐变范围,按照角度设置。

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

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

import 'package:flutter/material.dart';void main() {// Text4runApp(MyApp());
}class MyApp extends StatelessWidget {@overrideWidget 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);@overrideWidget 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 {@overrideWidget 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);@overrideWidget build(BuildContext context) {return Scaffold(appBar: AppBar(title: Text('Love Story'),),// 主体部分展示Rowbody: Center(child: Row(// 这个仍然适用,这里右到左,显示顺序与代码顺序颠倒textDirection: TextDirection.rtl,children: const <Widget>[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, minmainAxisSize: 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 <Widget>[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);@overrideWidget 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 {@overrideWidget 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 widgetchildren: 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 scaffolditems: const [ //items inside navigation barBottomNavigationBarItem(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);@overrideState<_Commodity> createState() => _CommodityState();
}class _CommodityState extends State<_Commodity> {// 存图片 + 文字final List goods = [];// 商品名字体final _GoodsNameFont = const TextStyle(fontSize: 18);// 商品简介字体final _GoodsIntroduceFont = const TextStyle(fontSize: 18);@overrideWidget build(BuildContext context) {return Scaffold(body: _buildFrame(),);}Widget _buildFrame() {final namelist = ["只因", "你", "太美", "迎面走来的你", "让我", "蠢蠢欲动", "就会", "爆炸"];final introductionlist = ["小黑子露出鸡脚了吧,你食不食油饼", "你干嘛啊啊啊啊啊啊啊   哎呦!!!"];return ListView.builder(padding: const EdgeInsets.all(26.0),// 对每一个商品都用itemBuilderitemBuilder: (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,//     ),// );// 也可以返回一个ListTilereturn 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;@overrideWidget build(BuildContext context) {// 给一个padding,用来处理容器和组件之间的距离的。return Padding(padding: const EdgeInsets.symmetric(vertical: 10.0),// 定死每个栏目的高度child: SizedBox(height: 100,// 先放方块(图片),再放文字堆叠child: Row(crossAxisAlignment: CrossAxisAlignment.start,// 分两块,一块放图片,一块放_ArticleDescription => 那堆文字的合集children: <Widget>[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;@overrideWidget build(BuildContext context) {return Column(crossAxisAlignment: CrossAxisAlignment.start,children: <Widget>[Expanded(child: Column(crossAxisAlignment: CrossAxisAlignment.start,children: <Widget>[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: <Widget>[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 {@overrideWidget 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);@overrideWidget 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 {@overrideWidget 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);@overrideWidget 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 {@overrideWidget 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);@overrideWidget 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 {@overrideWidget 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),];}@overrideWidget 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,// 格子之间的paddingpadding: const EdgeInsets.all(8),// 圆角childAspectRatio: 1,// 一整个输出children: _photos(context).map<Widget>((photo) {return _GridDemoPhotoItem(photo: photo,tileStyle: type,);}).toList(),),bottomNavigationBar: BottomNavigationBar( //bottom navigation bar on scaffolditems: 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,// 显示所有图标的labelshowUnselectedLabels: 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;@overrideWidget build(BuildContext context) {return FittedBox(// 适应大小fit: BoxFit.scaleDown,// 对齐方向:// bottomCenter bottomEnd bottomStart 底部居中,底部结尾,底部开始// center centerEnd centerStart// topCenter topEnd topStartalignment: AlignmentDirectional.centerStart,child: Text(text),);}
}class _GridDemoPhotoItem extends StatelessWidget {const _GridDemoPhotoItem({required this.photo,required this.tileStyle,});final _Photo photo;final GridListDemoType tileStyle;@overrideWidget 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 {@overrideWidget 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);@overrideWidget build(BuildContext context) {final List<String> entries = <String>['A', 'B', 'C', 'D', 'E', 'FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF'];final List<int> colorCodes = <int>[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下有几个itemsitemCount: entries.length),]),);}
}

1.6 Stack

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

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

几个注意点:

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

简单实现一个:

import 'package:flutter/material.dart';void main() {runApp(MyApp());
}class MyApp extends StatelessWidget {@overrideWidget 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);@overrideWidget 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 {@overrideWidget 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);@overrideWidget 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 <Widget>[Spacer(),ElevatedCardExample(),FilledCardExample(),OutlinedCardExample(),MyCardExample(),Spacer(),],),),),);
}class ElevatedCardExample extends StatelessWidget {const ElevatedCardExample({super.key});@overrideWidget 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});@overrideWidget 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});@overrideWidget 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);@overrideWidget build(BuildContext context) {return Center(child: Card(child: SizedBox(height: 500,width: 300,child: Column(children: <Widget>[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: <Widget>[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 {@overrideWidget 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);@overrideWidget 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: <Widget>[Spacer(),ElevatedCardExample(),FilledCardExample(),OutlinedCardExample(),MyCardExample(),Spacer(),],),),),);
}class ElevatedCardExample extends StatelessWidget {const ElevatedCardExample({super.key});@overrideWidget 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});@overrideWidget 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});@overrideWidget 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<Map> imageList = [{"asset":"assets/images/8.jpg"},{"asset":"assets/images/2.jpg"},{"asset":"assets/images/3.jpg"},{"asset":"assets/images/4.jpg"}];@overrideWidget build(BuildContext context) {return Center(child: Card(shape: RoundedRectangleBorder(//设置圆角borderRadius: BorderRadius.circular(5),),child: SizedBox(height: 500,width: 300,child: Column(children: <Widget>[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: <Widget>[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 {@overrideWidget 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);@overrideWidget 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],),),],),),));
}

Flutter移动应用开发 - 04 Flutter 常用 widget 整理相关推荐

  1. 微信小程序开发编辑器功能常用快捷键整理

    微信小程序开发编辑器功能常用快捷键整理 常用快捷键 Ctrl + L(选中当前行) Ctrl + Shift + L(选中所有匹配) Ctrl + D(选中匹配,按一次多选一个) Ctrl + U(回 ...

  2. flutter listview 滚动到底部_Flutter常用Widget详解(三)

    前言 前面两篇文章给大家介绍了Widget中对应原生开发中的一些常用基础控件,Text.TextField.Button.Dialog.Picker等,本篇我们将和大家一起学习ListView.Gri ...

  3. Flutter面试常见开发问题

    本文主要介绍Flutter面试常见开发问题 Flutter 使用了一种全新的方法,您可以使用 widgets代替 Views .Android 中的 View 主要是布局的一个元素,但在 Flutte ...

  4. android开发技术可行性,Flutter技术调研及可行性结论

    前言: 随着Flutter的快速发展,以及在国内外应用中作为跨平台方案使用的普及,Flutter吸引了无数开发者的眼光.我们也对Flutter从以下方向进行了技术方面的调研,并在项目中进行了接入开发实 ...

  5. 【Flutter】Flutter 混合开发 ( 混合开发中 Flutter 的 热重启 / 热加载 )

    文章目录 前言 一.混合开发中启用 Flutter 的 热重启 / 热加载 二.混合开发中 Flutter 的 热重启 / 热加载 命令测试 三.指定混合应用连接的设备 四.相关资源 前言 上一篇博客 ...

  6. Flutter基础(四)开发Flutter应用前需要掌握的Basic Widget

    本文首发于公众号「刘望舒」 关联系列 ReactNative入门系列 React Native组件 Flutter基础系列 前言 学完了Dart语言,接下来就可以学习Widget了,Flutter的U ...

  7. Flutter 学习笔记 (二) —— Flutter布局及常用widget总结

    前言 在Flutter里,UI控件就是Widget,Widget根据不同的功能可以分为结构元素(如按钮或菜单),文本样式(字体或者颜色方案),布局属性(如填充,对齐,居中),可以这么理解,一个flut ...

  8. Ubuntu18.04 Flutter开发环境搭建

    目录 flutter安装 android studio安装 Android Studio创建Flutter项目 运行应用程序 flutter安装 下载flutter https://flutter.d ...

  9. 为什么Flutter是跨平台开发的终极之选

    作者 | Anchal Malik 译者 | 王强 来源 | 前端之巅 跨平台开发是当下最受欢迎.应用最广泛的框架之一.能实现跨平台开发的框架也五花八门,让人眼花缭乱. 最流行的跨平台框架有 Xama ...

最新文章

  1. Java Web整合开发读书笔记
  2. Original error was: DLL load failed: 找不到指定的模块。--解决办法
  3. Kubernetes 弹性伸缩全场景解读(五) - 定时伸缩组件发布与开源
  4. 解决Docker上安装RabbitMQ后Web管理页面打不开的问题
  5. python文字识别并获取位置_python实现简单的文字识别
  6. shell 中数学计算总结
  7. 我妈在深圳的这些日子
  8. 从把事做对到做对的事
  9. 工频干扰频谱测量_EMC预认证测量的哀与愁
  10. 大学计算机文档基本操作实验的效果,上海工程技术大学计算机实验报告5
  11. Spring和Mybatis整合-原生dao开发
  12. windows驱动安装卸载的实用小工具-InstDrv.exe
  13. 使用instsrv.exe+srvany.exe将应用程序安装为windows服务的方法
  14. Android模仿通讯录
  15. datasupport类删除_关于xcode:我可以从iOS DeviceSupport删除数据吗?
  16. #年轻人找工作应该把钱放第一位吗#
  17. 【leetcode】算法题记录(111-120)
  18. JAVA、PHP统一社会信用代码、身份证号算法解析验证
  19. “砍价”技巧受用终生
  20. Ubuntu18.04下安装Nvidia驱动和CUDA10.1+CUDNN

热门文章

  1. linux 云输入法下载,搜狗云输入法!
  2. 亚马逊物流批量清货计划的优势你知道?
  3. dbeaver Can‘t create driver instance
  4. quota 详解---quota 是什么
  5. 山东政法学院计算机高考志愿填报代码,山东高考志愿填报:政法类院校明日开始政审...
  6. Mysql 的 WITH 语法
  7. 开源|优酷动态模板研发体系为分发提效30%
  8. python数据分析与挖掘实战(航空公司客户价值分析)
  9. 中国医科大学2021年12月《医学心理学》作业考核试题
  10. 你必须学会的几个常用网络测试命令