在上一篇文章Flutter教程-行星之路由/导航(6)中,我们完成列表页跳转到详情页内容,这篇文章我们将完成详情页部分,最终得到一个非常漂亮的行星详情页。加黑字体代表新增代码。
页面主要由三个部件构成:
- 背景图片
- 背景图片和背景色之间的渐变条
- 内容
figure 1. 全部
figure 2. 列表
figure 3. 详情
背景图片
① 在Planet.dart模型中添加picture属性
class Planet {
final int id;
final String name;
final String location;
final String distance;
final String gravity;
final String description;
final String image;
final String picture;
const Planet(
{this.id,
this.name,
this.location,
this.distance,
this.gravity,
this.description,
this.image,
this.picture
});
}
② 修改HomePageBody.dart中的数据源,添加描述(decription)和图片链接(picture),图片版权来自于作者Nasa,在此感谢作者
final List<Planet> planets = [
const Planet(
id: 1,
name: "火星",
location: "银河系",
distance: "220.0m km",
gravity: "3.7 m/s",
description:
"Mars is the fourth planet from the Sun and the second-smallest planet in the Solar System after Mercury. In English, Mars carries a name of the Roman god of war, and is often referred to as the 'Red Planet' because the reddish iron oxide prevalent on its surface gives it a reddish appearance that is distinctive among the astronomical bodies visible to the naked eye. Mars is a terrestrial planet with a thin atmosphere, having surface features reminiscent both of the impact craters of the Moon and the valleys, deserts, and polar ice caps of Earth.",
image: "assets/img/mars.png",
picture:
"https://www.nasa.gov/sites/default/files/thumbnails/image/pia21723-16.jpg"),
const Planet(
id: 2,
name: "海王星",
location: "银河系",
distance: "54.6m km",
gravity: "11.15 m/s",
description:
"Neptune is the eighth and farthest known planet from the Sun in the Solar System. In the Solar System, it is the fourth-largest planet by diameter, the third-most-massive planet, and the densest giant planet. Neptune is 17 times the mass of Earth and is slightly more massive than its near-twin Uranus, which is 15 times the mass of Earth and slightly larger than Neptune. Neptune orbits the Sun once every 164.8 years at an average distance of 30.1 astronomical units (4.50×109 km). It is named after the Roman god of the sea and has the astronomical symbol ♆, a stylised version of the god Neptune's trident",
image: "assets/img/neptune.png",
picture: "https://www.nasa.gov/sites/default/files/Styles/full_width_feature/public/images/110411main_Voyager2_280_yshires.jpg"),
const Planet(
id: 3,
name: "月球",
location: "银河系",
distance: "54.6m km",
gravity: "1.622 m/s",
description:
"The Moon is an astronomical body that orbits planet Earth, being Earth's only permanent natural satellite. It is the fifth-largest natural satellite in the Solar System, and the largest among planetary satellites relative to the size of the planet that it orbits (its primary). Following Jupiter's satellite Io, the Moon is second-densest satellite among those whose densities are known.",
image: "assets/img/moon.png",
picture:
"https://farm5.staticflickr.com/4086/5052125139_43c31b7012.jpg"),
const Planet(
id: 4,
name: "地球",
location: "银河系",
distance: "0m km",
gravity: "9.8 m/s",
description:
"Earth is the third planet from the Sun and the only object in the Universe known to harbor life. According to radiometric dating and other sources of evidence, Earth formed over 4 billion years ago. Earth's gravity interacts with other objects in space, especially the Sun and the Moon, Earth's only natural satellite. Earth revolves around the Sun in 365.26 days, a period known as an Earth year. During this time, Earth rotates about its axis about 366.26 times.",
image: "assets/img/earth.png",
picture: "https://www.nasa.gov/sites/default/files/styles/full_width_feature/public/thumbnails/image/iss042e340851_1.jpg"),
const Planet(
id: 5,
name: "水星",
location: "银河系",
distance: "54.6m km",
gravity: "3.7 m/s",
description:
"Mercury is the smallest and innermost planet in the Solar System. Its orbital period around the Sun of 88 days is the shortest of all the planets in the Solar System. It is named after the Roman deity Mercury, the messenger to the gods.",
image: "assets/img/mercury.png",
picture:
"https://c1.staticflickr.com/9/8105/8497927473_2845ae671e_b.jpg"),
];
③ 在DetailPage.dart中,添加用于创建背景图片部件的函数
- 背景图片宽度和屏幕一致,高度为300.0
- 创建Container,并设置constraint使高度尽量保持300.0
- 使用Image.network可以非常方便的加载网络图片
- BoxFit.cover可以尽量保证图片覆盖这个部件,Flutter还提供了多个枚举值供我们使用,建议大家一一尝试,并理解它们的区别。
- 运行效果如下:
figure 4. 背景图片
Container _getBackground() {
return new Container(
child: new Image.network(
planet.picture,
fit: BoxFit.cover,
height: 300.0,
),
);
}
渐变条
上面的背景图片和背景色之间过渡的有些生硬,因此我们决定添加一个非常漂亮过渡条。在之前的章节中介绍过LinearGradient部件的使用,这里我们继续利用它实现过度条。
在DetailPage.dart中,添加创建用于渐变条部件的函数
- 设置margin top为190.0,顶部向下偏移190.0
- 设置高度为110.0
- 设置stops为[0.0, 0.9],渐变色到90%的位置,剩余10%为实体色
- 运行效果如下:
Container _getGradient() {
return new Container(
margin: new EdgeInsets.only(top: 190.0),
height: 110.0,
decoration: new BoxDecoration(
gradient: new LinearGradient(
colors: [new Color(0x00736ab7), new Color(0xff736ab7)],
stops: [0.0, 0.9],
begin: const FractionalOffset(0.0, 0.0),
end: const FractionalOffset(0.0, 1.0),
)),
);
}
创建公共样式Style.dart
import 'package:flutter/material.dart';
class Style {
// 基础样式
static final baseTextStyle = const TextStyle(fontFamily: 'Poppins');
// 标题样式,基于基础样式
static final headerTextStyle = baseTextStyle.copyWith(
color: Colors.white, fontSize: 18.0, fontWeight: FontWeight.w600);
// 内容样式,基于基础样式
static final regularTextStyle = baseTextStyle.copyWith(
color: const Color(0xffb6b2df),
fontSize: 9.0,
fontWeight: FontWeight.w400);
// 副标题样式,基于内容样式
static final subHeaderTextStyle = regularTextStyle.copyWith(fontSize: 12.0);
}
创建分割线Separator.dart
import 'package:flutter/material.dart';
class Separator extends StatelessWidget {
@override
Widget build(BuildContext context) {
// TODO: implement build
return new Container(
margin: new EdgeInsets.symmetric(vertical: 8.0),
height: 2.0,
width: 8.0,
color: new Color(0xff00c6ff),
);
}
}
创建内容PlanetSummary.dart
import 'package:flutter/material.dart';
import 'package:demo/Planet.dart';
import 'package:demo/DetailPage.dart';
import 'package:demo/Style.dart';
class PlanetSummary extends StatelessWidget {
final Planet planet;
final bool horizontal;
PlanetSummary(this.planet, {this.horizontal = true});
PlanetSummary.vertical(this.planet) : horizontal = false;
// ① 下划线代表私有方法
// ② 生成图标、数值的部件
Widget _planetValue({String value, String image}) {
return new Row(
children: <Widget>[
// 图标
new Image.asset(
image,
height: 12.0,
),
// 间距8.0
new Container(
width: 8.0,
),
// 数值
new Text(
value,
style: Style.regularTextStyle,
),
],
);
}
@override
Widget build(BuildContext context) {
final planetCardContent = new Container(
margin: new EdgeInsets.fromLTRB(
76.0, horizontal ? 16.0 : 42.0, horizontal ? 16.0 : 70.0, 16.0),
// 铺满
constraints: new BoxConstraints.expand(),
child: new Column(
// 水平方向,自视图左对齐
crossAxisAlignment:
horizontal ? CrossAxisAlignment.start : CrossAxisAlignment.center,
children: <Widget>[
// 间距4.0
new Container(
height: 4.0,
),
// 行星名称
new Text(
planet.name,
style: Style.headerTextStyle,
),
// 间距10.0
new Container(
height: 10.0,
),
// 行星位置
new Text(
planet.location,
style: Style.subHeaderTextStyle,
),
// 横岗
new Container(
// 上下间距8.0
margin: new EdgeInsets.symmetric(vertical: 8.0),
height: 2.0,
width: 18.0,
color: new Color(0xff00c6ff),
),
// 距离和重力
new Row(
// Expanded Widget:Row/Column/Flex子控件铺满主轴方向
children: <Widget>[
new Expanded(
flex: horizontal ? 0 : 1,
child: _planetValue(
value: planet.distance,
image: 'assets/img/ic_distance.png')),
new Container(
width: 32.0,
),
new Expanded(
flex: horizontal ? 0 : 1,
child: _planetValue(
value: planet.gravity,
image: 'assets/img/ic_gravity.png'))
],)],),
);
final planetThumbnail = new Container(
// 上下向内偏移16.0
margin: new EdgeInsets.symmetric(vertical: 16.0),
// 水平方向靠左边线,垂直方向居中,等价于FractionalOffset(0.0, 0.5);
alignment: horizontal
? FractionalOffset.centerLeft
: FractionalOffset.topCenter,
child: new Hero(
tag: "planet-hero-${planet.id}",
child: new Image(
// 使用AssetImage Widget加载图片
image: new AssetImage(planet.image),
height: 92.0,
width: 92.0,
),
));
final planetCard = new Container(
height: horizontal ? 124.0 : 154.0,
// 左侧偏移46.0
margin: horizontal
? new EdgeInsets.only(left: 46.0)
: new EdgeInsets.only(top: 72.0),
// 设置样式
decoration: new BoxDecoration(
color: new Color(0xff333366),
// 设置为矩形
shape: BoxShape.rectangle,
// 设置圆角为8.0
borderRadius: new BorderRadius.circular(8.0),
// 设置阴影,向下偏移10.0,模糊半径10.0
boxShadow: [
new BoxShadow(
color: Colors.black12,
// 模糊半径10.0
blurRadius: 10.0,
// 向下偏移10.0
offset: new Offset(0.0, 10.0),
)
]),
child: planetCardContent,
);
return new GestureDetector(
onTap: horizontal
? () => Navigator.of(context).push(new PageRouteBuilder(
pageBuilder: (_, __, ___) => new DetailPage(planet),
))
: null,
child: new Container(
height: horizontal ? 120.0 : 254.0,
// EdgeInsets.symmetric等同于EdgeInsets.only(top: 16.0, bottom: 16.0, left: 24.0, right: 24.0)
margin: const EdgeInsets.symmetric(
// 上下偏移16.0
vertical: 16.0,
// 左右偏移24.0
horizontal: 24.0,
),
// Stack Widget用于创建上下重叠部件
child: new Stack(
children: <Widget>[planetCard, planetThumbnail],
)));}
}
内容接下来我们完成最后的工作。
① 在DetailPage.dart中,添加创建用于内容部件的函数
Widget _getContent() {
return new ListView(
padding: new EdgeInsets.fromLTRB(0.0, 72.0, 0.0, 32.0),
children: <Widget>[
new PlanetSummary(planet, horizontal: false),
new Container(
padding: new EdgeInsets.symmetric(horizontal: 32.0),
child: new Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: <Widget>[
new Text(
"简介",
style: Style.headerTextStyle,
),
new Separator(),
new Text(planet.description, style: Style.subHeaderTextStyle)
]))]);
}
② 修改build函数
Widget build(BuildContext context) {
return new Scaffold(
appBar: new AppBar(
title: new Text("详情页"),
),
body: new Container(
constraints: new BoxConstraints.expand(),
color: new Color(0xff736ab7),
child: new Stack(
children: <Widget>[
_getBackground(),
_getGradient(),
_getContent()
])));
}
③ 运行后的效果如下:
项目地址,欢迎Star
git@github.com:hao2008/flutter-demo.git
系列文章谈谈Flutter
Flutter教程-行星(1)
Flutter教程-行星之创建卡片Widget(2)
Flutter教程-行星之卡片信息(3)
Flutter教程-行星之卡片列表(4)
Flutter教程-行星之路由/导航(5)
Flutter教程-行星之路由/导航(6)
结束语Flutter教程-行星系列文章到此结束,不知道你是否跟随着一起完成了这个Demo,是否感受到了Flutter开发移动应用的乐趣,是否希望进一步实践Flutter。其实,郝先生想说的是,不要犹豫,不要观望,有或者没有想法,都应该立即付诸行动。目前大多数公司仍然是苹果、安卓两个团队并行开发的模式,从人力消耗和产品一致性上来说,并不乐观,加之国内经济下滑,国内公司要么崩塌,要么变革。哈哈,忽略郝先生刚才说的胡话,讲真的,用更少的人力物力开发跨平台移动应用值得尝试,而且听说阿里的闲鱼技术团队,要实现三端一体化,也就是说移动端的开发人员可以完成部分后端工作,多么美好的愿景,学会dart语言走遍天下都不怕。
还在等什么?实践吧!!
,