1、效果圖

3.gif
2、本節(jié)的知識點
2-1、為靜態(tài)資源創(chuàng)建配置類
比如本節(jié)內(nèi)容每張圖片都有名字和描述,那我們統(tǒng)一配置一個類
class TravelBean {
String name;//名字
String location;//描述
String url;//靜態(tài)資源的地址
TravelBean(this.name, this.location, this.url);
//我們可以添加靜態(tài)方法 返回指定的內(nèi)容
static List <TravelBean > generateTravelBean(){
return [
TravelBean (*,*,*)、、、
]
}
}
2-2、獲取手機屏幕劉海(即手機型號等狀態(tài)欄)的高度
有時候我們可能不會設(shè)置appBar,讓背景圖穿透AppBar
例如抖音的個人中心背景圖它是直接穿透狀態(tài)欄的,我們本次的詳情頁面也是穿透
MediaQuery.of(context).padding.top
2-3、獲取屏幕的寬度/高度
1、double.infinity //無限大
2、MediaQuery.of(context).size.width /高度:.size.height
2-4、跨頁面動畫

hero.gif
本節(jié)效果的首頁頂部圖片切換路由至詳情頁的頂部動畫
使用Hero組件:注意此效果是跨頁面即跨路由
Hero源碼
const Hero({
Key key,
@required this.tag, //必選參數(shù)規(guī)定兩個頁面之間的Hero標(biāo)簽
this.createRectTween,
this.flightShuttleBuilder,
this.placeholderBuilder,
this.transitionOnUserGestures = false,//IOS滑動切換路由是否支持
@required this.child,//子節(jié)點
}) : assert(tag != null),
assert(transitionOnUserGestures != null),
assert(child != null),
super(key: key);
具體使用方法可以查看Flutter進階:在應(yīng)用中實現(xiàn) Hero(飛行) 動畫
2-4、獲取AppBar的高度
kToolbarHeight
2-5、設(shè)置沉浸式導(dǎo)航欄
通常我們使用的都是
material風(fēng)格的樣式,就會造成狀態(tài)這一塊有陰影
21
if (Platform.isAndroid) {
SystemChrome.setSystemUIOverlayStyle(SystemUiOverlayStyle(
statusBarColor: Colors.white,
statusBarBrightness: Brightness.dark,
statusBarIconBrightness: Brightness.dark));
}
2-6、設(shè)置狀態(tài)欄字體黑白顏色
AnnotatedRegion<SystemUiOverlayStyle>(
value: SystemUiOverlayStyle(
statusBarColor: Colors.transparent,
statusBarIconBrightness: Brightness.dark,
statusBarBrightness: Brightness.dark,
)
2-7、本節(jié)的重點:CustomScrollView組件
CustomScrollView是可以使用Sliver來自定義滾動模型(效果)的組件。它可以包含多種滾動模型,舉個例子,假設(shè)有一個頁面,頂部需要一個GridView,底部需要一個ListView,而要求整個頁面的滑動效果是統(tǒng)一的,即它們看起來是一個整體。如果使用GridView+ListView來實現(xiàn)的話,就不能保證一致的滑動效果,因為它們的滾動效果是分離的,所以這時就需要一個"膠水",把這些彼此獨立的可滾動組件"粘"起來,而CustomScrollView的功能就相當(dāng)于“膠水”。
flutter中文網(wǎng)- CustomScrollView
3、源碼剖析
//HomePage
import 'package:flutter/material.dart';
import 'TravelBean.dart';
import 'DetailPage.dart';
class HomePage extends StatelessWidget {
@override
Widget build(BuildContext context) {
return Scaffold(
backgroundColor: Colors.white,
appBar: AppBar(
backgroundColor: Colors.white,
elevation: 0,
actions: <Widget>[
Padding(
padding: EdgeInsets.only(right: 15),
child: Icon(
Icons.menu,
color: Colors.black,
),
)
],
),
body: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: <Widget>[
Padding(
padding: EdgeInsets.symmetric(horizontal: 15, vertical: 10),
child: Text(
"Travel Blog",
style: TextStyle(
color: Colors.black,
fontSize: 30,
),
),
),
Expanded(
flex: 2,
child: TravelWidget(),
),
Padding(
padding: EdgeInsets.symmetric(horizontal: 15, vertical: 15),
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: <Widget>[
Text(
"Most Popular",
style: TextStyle(
color: Colors.black,
fontSize: 20,
),
),
Text(
"View all",
style: TextStyle(
color: Colors.deepOrange,
fontSize: 20,
),
),
],
),
),
Expanded(
flex: 1,
child: MostPopularWidget(),
)
],
),
);
}
}
class MostPopularWidget extends StatelessWidget {
List<TravelBean> _list = TravelBean.generateMostPopularBean();
@override
Widget build(BuildContext context) {
return ListView.builder(
padding: EdgeInsets.symmetric(horizontal: 15),
scrollDirection: Axis.horizontal,
itemBuilder: (context, index) {
var bean = _list[index];
return GestureDetector(
onTap: () {
Navigator.of(context).push(MaterialPageRoute(builder: (context) {
return DetailPage(bean);
}));
},
child: Hero(
tag: bean.url,
child: Stack(
children: <Widget>[
Padding(
padding: const EdgeInsets.only(bottom: 30, right: 10),
child: ClipRRect(
borderRadius: BorderRadius.circular(5),
child: Image.asset(
bean.url,
width: 170,
fit: BoxFit.cover,
),
),
),
Positioned(
bottom: 50,
left: 15,
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: <Widget>[
Material(
color: Colors.transparent,
child: Text(
bean.location,
style: TextStyle(
color: Colors.white,
fontSize: 15,
),
),
),
Material(
color: Colors.transparent,
child: Text(
bean.name,
style: TextStyle(
color: Colors.white,
fontSize: 15,
),
),
),
],
),
),
],
),
),
);
},
itemCount: _list.length,
);
}
}
class TravelWidget extends StatelessWidget {
List<TravelBean> _list = TravelBean.generateTravelBean();
@override
Widget build(BuildContext context) {
return PageView.builder(
controller: PageController(viewportFraction: 0.9),
itemBuilder: (context, index) {
var bean = _list[index];
return GestureDetector(
onTap: () {
Navigator.of(context).push(MaterialPageRoute(builder: (context) {
return DetailPage(bean);
}));
},
child: Hero(
tag: bean.url,
child: Stack(
children: <Widget>[
Padding(
padding: const EdgeInsets.only(bottom: 30, right: 10),
child: ClipRRect(
borderRadius: BorderRadius.circular(5),
child: Image.asset(
bean.url,
width: MediaQuery.of(context).size.width,
fit: BoxFit.cover,
),
),
),
Positioned(
bottom: 80,
left: 15,
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: <Widget>[
Material(
color: Colors.transparent,
child: Text(
bean.location,
style: TextStyle(
color: Colors.black54,
fontSize: 15,
),
),
),
Material(
color: Colors.transparent,
child: Text(
bean.name,
style: TextStyle(
color: Colors.black,
fontSize: 20,
),
),
),
],
),
),
Positioned(
bottom: 0,
right: 30,
child: Container(
width: 60,
height: 60,
decoration: BoxDecoration(
color: Colors.red,
borderRadius: BorderRadius.circular(30),
),
child: Icon(
Icons.arrow_forward,
color: Colors.white,
size: 30,
),
),
)
],
),
),
);
},
itemCount: _list.length,
);
}
}
//DetailPage.dart
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:flutter_ui/TravelBean.dart';
class DetailPage extends StatefulWidget {
final TravelBean bean;
DetailPage(this.bean);
@override
_DetailPageState createState() => _DetailPageState();
}
class _DetailPageState extends State<DetailPage> {
final double expanded_height = 400;
final double rounded_container_height = 50;
@override
Widget build(BuildContext context) {
return Scaffold(
body: Stack(
children: <Widget>[
CustomScrollView(
slivers: <Widget>[
_buildSliverHead(),
SliverToBoxAdapter(
child: _buildDetail(),
)
],
),
Padding(
padding: EdgeInsets.only(top: MediaQuery.of(context).padding.top),
child: SizedBox(
height: kToolbarHeight,
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: <Widget>[
Padding(
padding: EdgeInsets.symmetric(horizontal: 15),
child: IconButton(
icon: Icon(Icons.arrow_back, color: Colors.white),
onPressed: () {
Navigator.of(context).pop();
},
),
),
Padding(
padding: EdgeInsets.symmetric(horizontal: 15),
child: Icon(Icons.menu, color: Colors.white),
)
],
)),
)
],
),
);
}
Widget _buildDetail() {
return Container(
color: Colors.white,
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: <Widget>[
_bulidUserInfo(),
Padding(
padding: EdgeInsets.symmetric(vertical: 15, horizontal: 15),
child: Text(
"The balearic Lsnaled,The Lsnaled,The balea balearic Lsnaled,"
"The balearic Lsnaled,Lsnaled,The balea The balearic Lsnaled,"
"The balearic Lsnaled,Lsnaled,The balea The balearic Lsnaled,"
"The balearic Lsnaled,Lsnaled,The balea The balearic Lsnaled,"
"The balearic Lsnaled,Lsnaled,The balea The balearic Lsnaled,"
"The balearic Lsnaled,The balearic Lsnaled,The balea Lsnaled,"
"The balearic Lsnaled,The balearic Lsnaled,",
style: TextStyle(
color: Colors.black38,
height: 1.4,
fontSize: 14,
decoration: TextDecoration.none),
),
),
Padding(
padding: EdgeInsets.only(left: 15, right: 30, bottom: 10, top: 10),
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: <Widget>[
Text(
"Featured",
style: TextStyle(
color: Colors.black,
fontWeight: FontWeight.bold,
fontSize: 18,
letterSpacing: 1.6,
decoration: TextDecoration.none),
),
Text(
"View All",
style: TextStyle(
color: Colors.deepOrange,
fontWeight: FontWeight.bold,
fontSize: 18,
letterSpacing: 1.6,
decoration: TextDecoration.none),
)
],
),
),
Container(
padding: EdgeInsets.symmetric(horizontal: 15, vertical: 0),
child: FeaturedWidget(),
height: 130,
),
Padding(
padding: EdgeInsets.symmetric(vertical: 15, horizontal: 15),
child: Text(
"The balearic Lsnaled,The Lsnaled,The balea balearic Lsnaled,"
"The balearic Lsnaled,Lsnaled,The balea The balearic Lsnaled,"
"The balearic Lsnaled,Lsnaled,The balea The balearic Lsnaled,"
"The balearic Lsnaled,Lsnaled,The balea The balearic Lsnaled,"
"The balearic Lsnaled,Lsnaled,The balea The balearic Lsnaled,"
"The balearic Lsnaled,The balearic Lsnaled,The balea Lsnaled,"
"The balearic Lsnaled,The balearic Lsnaled,",
style: TextStyle(
color: Colors.black38,
height: 1.4,
fontSize: 14,
decoration: TextDecoration.none),
),
),
],
),
);
}
Widget _bulidUserInfo() {
return Padding(
padding: EdgeInsets.symmetric(vertical: 10, horizontal: 8),
child: Row(
children: <Widget>[
CircleAvatar(
backgroundColor: Colors.white,
child: ClipRRect(
borderRadius: BorderRadius.circular(50),
child: Image.asset(
widget.bean.url,
width: 50,
height: 50,
fit: BoxFit.cover,
),
),
),
SizedBox(width: 10),
Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: <Widget>[
Text(
widget.bean.name,
style: TextStyle(
color: Colors.black,
fontSize: 18,
decoration: TextDecoration.none,
fontWeight: FontWeight.bold),
),
Text(
"Writer,Wonderlust",
style: TextStyle(
color: Colors.black,
fontSize: 14,
decoration: TextDecoration.none,
fontWeight: FontWeight.normal),
)
],
),
Spacer(),
Icon(
Icons.share,
color: Colors.black54,
)
],
),
);
}
Widget _buildSliverHead() {
return SliverPersistentHeader(
delegate: DetailSliverDelegate(
expanded_height, widget.bean, rounded_container_height));
}
}
class FeaturedWidget extends StatelessWidget {
List<TravelBean> _list = TravelBean.generateMostPopularBean();
@override
Widget build(BuildContext context) {
return ListView.builder(
itemBuilder: (context, index) {
return Container(
width: 120,
height: 130,
margin: EdgeInsets.only(right: 15),
child: Image.asset(
_list[index].url,
width: 120,
height: 130,
fit: BoxFit.cover,
),
);
},
itemCount: _list.length,
scrollDirection: Axis.horizontal);
}
}
class DetailSliverDelegate extends SliverPersistentHeaderDelegate {
final double expandedHeight;
final TravelBean bean;
final double rounded_container_height;
DetailSliverDelegate(
this.expandedHeight, this.bean, this.rounded_container_height);
@override
Widget build(
BuildContext context, double shrinkOffset, bool overlapsContent) {
return AnnotatedRegion<SystemUiOverlayStyle>(
value: SystemUiOverlayStyle(
statusBarColor: Colors.transparent,
statusBarIconBrightness: Brightness.dark,
statusBarBrightness: Brightness.dark,
),
child: Stack(
children: <Widget>[
Hero(
tag: bean.url,
child: Image.asset(
bean.url,
width: MediaQuery.of(context).size.width,
fit: BoxFit.cover,
)),
Positioned(
child: Container(
width: MediaQuery.of(context).size.width,
height: rounded_container_height,
decoration: BoxDecoration(
color: Colors.white,
borderRadius: BorderRadius.only(
topLeft: Radius.circular(30),
topRight: Radius.circular(30))),
),
top: expandedHeight - rounded_container_height + 5 - shrinkOffset,
left: 0,
),
Positioned(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: <Widget>[
Text(
bean.name,
style: TextStyle(
color: Colors.white,
fontSize: 20,
decoration: TextDecoration.none),
),
Text(
bean.location,
style: TextStyle(
color: Colors.white,
fontSize: 15,
decoration: TextDecoration.none),
)
],
),
top: expandedHeight - 120 - shrinkOffset,
left: 30,
),
],
),
);
}
@override
double get maxExtent => expandedHeight;
@override
double get minExtent => 0;
@override
bool shouldRebuild(SliverPersistentHeaderDelegate oldDelegate) {
return true;
}
}
//main.dart
import 'dart:io';
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'HomePage.dart';
void main(){
runApp(MyApp());
if(Platform.isAndroid){
SystemChrome.setSystemUIOverlayStyle(SystemUiOverlayStyle(statusBarColor:Colors.white,statusBarBrightness: Brightness.dark,statusBarIconBrightness: Brightness.dark));
}
}
class MyApp extends StatelessWidget {
const MyApp({Key key}) : super(key: key);
@override
Widget build(BuildContext context) {
return MaterialApp(
title: "精美UI",
debugShowCheckedModeBanner: false,
home: HomePage(),
);
}
}
//TravelBean.dart
class TravelBean {
String name;
String location;
String url;
TravelBean(this.name, this.location, this.url);
static List<TravelBean> generateTravelBean() {
return [
TravelBean("Peach", "Spain ES1", "assets/images/top1.jpg"),
TravelBean("Grassland", "Spain ES2", "assets/images/top2.jpg"),
TravelBean("Starry sky", "Spain ES3", "assets/images/top3.jpg"),
TravelBean("Beauty Pic", "Spain ES4", "assets/images/top4.jpg"),
];
}
static List<TravelBean> generateMostPopularBean() {
return [
TravelBean("Peach", "Spain ES", "assets/images/bottom1.jpg"),
TravelBean("Grassland", "Spain ES", "assets/images/bottom2.jpg"),
TravelBean("Starry sky", "Spain ES", "assets/images/bottom3.jpg"),
TravelBean("Beauty Pic", "Spain ES", "assets/images/bottom4.jpg"),
];
}
}
