super.dispose();
}
// TODO:添加函数以确定并改变 front layer 可见性(104)
部件生命周期
仅在部件成为其渲染树的一部分之前会调用一次
initState()
方法。只有在部件从树中移除时才会调用一次dispose()
方法。
AnimationController 用来配合 Animation,并提供播放、反向和停止动画的 API。现在我们需要使用某个方法来移动它。
添加函数以确定并改变 front layer 的可见性:
// TODO:添加函数以确定并改变 front layer 的可见性(104)
bool get _frontLayerVisible {
final AnimationStatus status = _controller.status;
return status == AnimationStatus.completed ||
status == AnimationStatus.forward;
}
void _toggleBackdropLayerVisibility() {
_controller.fling(
velocity: _frontLayerVisible ? -_kFlingVelocity : _kFlingVelocity);
}
将 backLayer 包裹在 ExcludeSemantics 部件中。当 back layer 不可见时,此部件将从语义树中剔除 backLayer 的菜单项。
return Stack(
key: _backdropKey,
children: [
// TODO:将 backLayer 包裹在 ExcludeSemantics 部件中(104)
ExcludeSemantics(
child: widget.backLayer,
excluding: _frontLayerVisible,
),
…
修改 _buildStack()
方法使其持有一个 BuildContext 和 BoxConstraints。同时包含一个使用 RelativeRectTween 动画的 PositionedTransition:
// TODO:为 _buildStack 添加 BuildContext 和 BoxConstraints 参数(104)
Widget _buildStack(BuildContext context, BoxConstraints constraints) {
const double layerTitleHeight = 48.0;
final Size layerSize = constraints.biggest;
final double layerTop = layerSize.height - layerTitleHeight;
// TODO:创建一个 RelativeRectTween 动画(104)
Animation layerAnimation = RelativeRectTween(
begin: RelativeRect.fromLTRB(
0.0, layerTop, 0.0, layerTop - layerSize.height),
end: RelativeRect.fromLTRB(0.0, 0.0, 0.0, 0.0),
).animate(_controller.view);
return Stack(
key: _backdropKey,
children: [
ExcludeSemantics(
child: widget.backLayer,
excluding: _frontLayerVisible,
),
// TODO:添加一个 PositionedTransition(104)
PositionedTransition(
rect: layerAnimation,
child: _FrontLayer(
// TODO:在 _BackdropState 上实现 onTap 属性(104)
child: widget.frontLayer,
),
),
],
);
}
最后,返回一个使用 _buildStack
作为其 builder 的 LayoutBuilder 部件,而不是为 Scaffold 的主体调用 _buildStack
函数:
return Scaffold(
appBar: appBar,
// TODO:返回一个 LayoutBuilder 部件(104)
body: LayoutBuilder(builder: _buildStack),
);
我们使用 LayoutBuilder 将 front/back 堆栈的构建延迟到布局阶段,以便我们可以合并背景的实际整体高度。LayoutBuilder 是一个特殊的部件,其构建器回调提供了大小约束。
LayoutBuilder
部件树通过遍历叶结点来组织布局。约束在树下传递,但是在叶结点根据约束返回其大小之前通常不会计算大小。叶子点无法知道它的父母的大小,因为它尚未计算。
当部件必须知道其父部件的大小以便自行布局(且父部件大小不依赖于子部件)时,LayoutBuilder 就派上用场了。它使用一个方法来返回部件。
了解有关更多信息,请查看 LayoutBuilder 类文档。
在 build()
方法中,将应用栏中的前导菜单图标转换为 IconButton,并在点击按钮时使用它来切换 front layer 的可见性。
// TODO:用 IconButton 替换 leading 菜单图标(104)
leading: IconButton(
icon: Icon(Icons.menu),
onPressed: _toggleBackdropLayerVisibility,
),
在模拟器中重载并点击菜单按钮。
front layer 在向下移动(滑动)。但如果向下看,则会出现红色错误和溢出错误。这是因为 AsymmetricView 被这个动画挤压并变小,反过来使得 Column 的空间更小。最终,Column 不能用给定的空间自行排列并导致错误。如果我们用 ListView 替换 Column,则移动时列的尺寸仍然保持不变。
在 ListView 中包裹产品列项
在 supplemental/product_columns.dart
中,将 OneProductCardColumn
的 Column 替换成 ListView:
class OneProductCardColumn extends StatelessWidget {
OneProductCardColumn({this.product});
final Product product;
@override
Widget build(BuildContext context) {
// TODO:用 ListView 替换 Column(104)
return ListView(
reverse: true,
children: [
SizedBox(
height: 40.0,
),
ProductCard(
product: product,
),
],
);
}
}
Column 包含 MainAxisAlignment.end
。要使得从底部开始布局,使用 reverse: true
。其孩子的顺序将翻转以弥补变化。
重载并点击菜单按钮。
OneProductCardColumn 上的灰色溢出警告消失了!现在让我们修复另一个问题。
在 supplemental/product_columns.dart
中修改 imageAspectRatio
的计算方式,并将 TwoProductCardColumn
中的 Column 替换成 ListView:
// TODO:修改 imageAspectRatio 的计算方式(104)
double imageAspectRatio =
(heightOfImages >= 0.0 && constraints.biggest.width > heightOfImages)
? constraints.biggest.width / heightOfImages
: 33 / 49;
// TODO:用 ListView 替换 Column(104)
return ListView(
children: [
Padding(
padding: EdgeInsetsDirectional.only(start: 28.0),
child: top != null
? ProductCard(
imageAspectRatio: imageAspectRatio,
product: top,
)
: SizedBox(
height: heightOfCards,
),
),
SizedBox(height: spacerHeight),
Padding(
padding: EdgeInsetsDirectional.only(end: 28.0),
child: ProductCard(
imageAspectRatio: imageAspectRatio,
product: bottom,
),
),
],
);
});
我们还为 imageAspectRatio
添加了一些安全性。
重载。然后点击菜单按钮。
现在已经没有溢出了。
7. 在 back layer 上添加菜单
菜单是由可点击文本项组成的列表,当发生点击事件时通知监听器。在此小节,你将添加一个类别过滤菜单。
添加菜单
在 front layer 添加菜单并在 back layer 添加互动按钮。
创建名为 lib/category_menu_page.dart
的新文件:
import ‘package:flutter/material.dart’;
import ‘package:meta/meta.dart’;
import ‘colors.dart’;
import ‘model/product.dart’;
class CategoryMenuPage extends StatelessWidget {
final Category currentCategory;
final ValueChanged onCategoryTap;
final List _categories = Category.values;
const CategoryMenuPage({
Key key,
@required this.currentCategory,
@required this.onCategoryTap,
}) : assert(currentCategory != null),
assert(onCategoryTap != null);
Widget _buildCategory(Category category, BuildContext context) {
final categoryString =
category.toString().replaceAll(‘Category.’, ‘’).toUpperCase();
final ThemeData theme = Theme.of(context);
return GestureDetector(
onTap: () => onCategoryTap(category),
child: category == currentCategory
? Column(
children: [
SizedBox(height: 16.0),
Text(
categoryString,
style: theme.textTheme.body2,
textAlign: TextAlign.center,
),
SizedBox(height: 14.0),
Container(
width: 70.0,
height: 2.0,
color: kShrinePink400,
),
],
)
: Padding(
padding: EdgeInsets.symmetric(vertical: 16.0),
child: Text(
categoryString,
style: theme.textTheme.body2.copyWith(
color: kShrineBrown900.withAlpha(153)
),
textAlign: TextAlign.center,
),
),
);
}
@override
Widget build(BuildContext context) {
return Center(
child: Container(
padding: EdgeInsets.only(top: 40.0),
color: kShrinePink100,
child: ListView(
children: _categories
.map((Category c) => _buildCategory(c, context))
.toList()),
),
);
}
}
它是一个 GestureDetector,它包含一个 Column,其孩子是类别名称。下划线用于指示所选的类别。
在 app.dart
中,将 ShrineApp 部件从 stateless 转换成 stateful。
- 高亮
ShrineApp.
- 按 alt(option)+ enter
- 选择 “Convert to StatefulWidget”。
- 将 ShrineAppState 类更改为 private(
_ShrineAppState
)。要从 IDE 主菜单执行此操作,请选择 Refactor > Rename。或者在代码中,您可以高亮显示类名 ShrineAppState,然后右键单击并选择 Refactor > Rename。输入_ShrineAppState
以使该类成为私有。
在 app.dart
中,为选择的类别添加一个变量 _ShrineAppState
,并在点击时添加一个回调:
// TODO:将 ShrineApp 转换成 stateful 部件(104)
class _ShrineAppState extends State {
Category _currentCategory = Category.all;
void _onCategoryTap(Category category) {
setState(() {
_currentCategory = category;
});
}
然后将 back layer 修改为 CategoryMenuPage。
在 app.dart
中引入 CategoryMenuPage:
import ‘backdrop.dart’;
import ‘colors.dart’;
import ‘home.dart’;
import ‘login.dart’;
import ‘category_menu_page.dart’;
import ‘model/product.dart’;
import ‘supplemental/cut_corners_border.dart’;
在 build()
方法,将 backlayer 字段修改成 CategoryMenuPage 并让 currentCategory 字段持有实例变量。
home: Backdrop(
// TODO:让 currentCategory 字段持有 _currentCategory(104)
currentCategory: _currentCategory,
// TODO:为 frontLayer 传递 _currentCategory(104)
frontLayer: HomePage(),
// TODO:将 backLayer 修改成 CategoryMenuPage(104)
backLayer: CategoryMenuPage(
currentCategory: _currentCategory,
onCategoryTap: _onCategoryTap,
),
frontTitle: Text(‘SHRINE’),
backTitle: Text(‘MENU’),
),
重载并点击菜单按钮。
你点击了菜单选项,然而什么也没有发生…让我们修复它。
在 home.dart
中,为 Category 添加一个变量并将其传递给 AsymmetricView。
import ‘package:flutter/material.dart’;
import ‘model/products_repository.dart’;
import ‘model/product.dart’;
import ‘supplemental/asymmetric_view.dart’;
class HomePage extends StatelessWidget {
// TODO:为 Category 添加一个变量(104)
final Category category;
const HomePage({this.category: Category.all});
@override
Widget build(BuildContext context) {
// TODO:为 Category 添加一个变量并将其传递给 AsymmetricView(104)
return AsymmetricView(products: ProductsRepository.loadProducts(category));
}
}
在 app.dart
中为 frontLayer
传递 _currentCategory
:
// TODO:为 frontLayer 传递 _currentCategory(104)
frontLayer: HomePage(category: _currentCategory),
重载。点击模拟器中的菜单按钮并选择一个类别。
点击菜单图标以查看产品。他们被过滤了!
选择菜单项后关闭 front layer
在 backdrop.dart
中,为 BackdropState
重写 didUpdateWidget()
方法:
// TODO:为 didUpdateWidget() 添加重写方法(104)
@override
void didUpdateWidget(Backdrop old) {
super.didUpdateWidget(old);
if (widget.currentCategory != old.currentCategory) {
_toggleBackdropLayerVisibility();
} else if (!_frontLayerVisible) {
_controller.fling(velocity: _kFlingVelocity);
}
}
热重载,然后点击菜单图标并选择一个类别。菜单应该自动关闭,然后你将看到所选择类别的物品。现在同样地将这个功能添加到 front layer 。
切换 front layer
在 backdrop.dart
中,给 backdrop layer 添加一个 on-tap 回调:
class _FrontLayer extends StatelessWidget {
// TODO:添加 on-tap 回调(104)
const _FrontLayer({
Key key,
this.onTap, // 新增代码
this.child,
}) : super(key: key);
final VoidCallback onTap; // 新增代码
final Widget child;
然后将一个 GestureDetector 添加到 _FrontLayer
的孩子 Column 的子节点中:
child: Column(
crossAxisAlignment: CrossAxisAlignment.stretch,
children: [
// TODO:添加一个 GestureDetector(104)
GestureDetector(
behavior: HitTestBehavior.opaque,
onTap: onTap,
child: Container(
height: 40.0,
alignment: AlignmentDirectional.centerStart,
),
),
Expanded(
child: child,
),
],
),
然后在 _buildStack()
方法的 _BackdropState
中实现新的 onTap
属性:
PositionedTransition(
rect: layerAnimation,
child: _FrontLayer(
// TODO:在 _BackdropState 中实现 onTap 属性(104)
onTap: _toggleBackdropLayerVisibility,
child: widget.frontLayer,
),
),
重载并点击 front layer 的顶部。每次你点击 front layer 顶部时都它应该打开或者关闭。
8. 添加品牌图标
品牌肖像也应该延伸到熟悉的图标。让我们自定义显示图标并将其与我们的标题合并,以获得独特的品牌外观。
修改菜单按钮图标
在 backdrop.dart
中,新建 _BackdropTitle
类。
// TODO:添加 _BackdropTitle 类(104)
class _BackdropTitle extends AnimatedWidget {
final Function onPress;
final Widget frontTitle;
final Widget backTitle;
const _BackdropTitle({
Key key,
Listenable listenable,
this.onPress,
@required this.frontTitle,
@required this.backTitle,
}) : assert(frontTitle != null),
assert(backTitle != null),
super(key: key, listenable: listenable);
@override
Widget build(BuildContext context) {
final Animation animation = this.listenable;
return DefaultTextStyle(
style: Theme.of(context).primaryTextTheme.title,
softWrap: false,
overflow: TextOverflow.ellipsis,
child: Row(children: [
// 品牌图标
SizedBox(
width: 72.0,
child: IconButton(
padding: EdgeInsets.only(right: 8.0),
onPressed: this.onPress,
icon: Stack(children: [
Opacity(
opacity: animation.value,
child: ImageIcon(AssetImage(‘assets/slanted_menu.png’)),
),
FractionalTranslation(
translation: Tween(
begin: Offset.zero,
end: Offset(1.0, 0.0),
).evaluate(animation),
child: ImageIcon(AssetImage(‘assets/diamond.png’)),
)]),
),
),
// 在这里,我们在 backTitle 和 frontTitle 之间是实现自定义的交叉淡入淡出效果
// 这使得两个文本之间能够平滑过渡。
Stack(
children: [
Opacity(
opacity: CurvedAnimation(
parent: ReverseAnimation(animation),
curve: Interval(0.5, 1.0),
).value,
child: FractionalTranslation(
translation: Tween(
begin: Offset.zero,
end: Offset(0.5, 0.0),
).evaluate(animation),
child: backTitle,
),
),
Opacity(
opacity: CurvedAnimation(
parent: animation,
curve: Interval(0.5, 1.0),
).value,
child: FractionalTranslation(
translation: Tween(
begin: Offset(-0.25, 0.0),
end: Offset.zero,
).evaluate(animation),
child: frontTitle,
),
),
],
)
]),
);
}
}
_BackdropTitle
是一个自定义部件,它将替换 AppBar
里 title
参数的 Text
部件。它有一个动画菜单图标和前后标题之间的动画过渡。动画菜单图标将使用新资源。因此必须将对新 slanted_menu.png
的引用添加到 pubspec.yaml
中。
assets:
- assets/diamond.png
- assets/slanted_menu.png
- packages/shrine_images/0-0.jpg
自我介绍一下,小编13年上海交大毕业,曾经在小公司待过,也去过华为、OPPO等大厂,18年进入阿里一直到现在。
深知大多数Android工程师,想要提升技能,往往是自己摸索成长或者是报班学习,但对于培训机构动则几千的学费,着实压力不小。自己不成体系的自学效果低效又漫长,而且极易碰到天花板技术停滞不前!
因此收集整理了一份《2024年Android移动开发全套学习资料》,初衷也很简单,就是希望能够帮助到想自学提升又不知道该从何学起的朋友,同时减轻大家的负担。
既有适合小白学习的零基础资料,也有适合3年以上经验的小伙伴深入学习提升的进阶课程,基本涵盖了95%以上Android开发知识点,真正体系化!
由于文件比较大,这里只是将部分目录大纲截图出来,每个节点里面都包含大厂面经、学习笔记、源码讲义、实战项目、讲解视频,并且后续会持续更新
如果你觉得这些内容对你有帮助,可以添加V:vip204888 备注Android获取(资料价值较高,非无偿)
最后:学习总结——Android框架体系架构知识脑图(纯手绘xmind文档)
学完之后,若是想验收效果如何,其实最好的方法就是可自己去总结一下。比如我就会在学习完一个东西之后自己去手绘一份xmind文件的知识梳理大纲脑图,这样也可方便后续的复习,且都是自己的理解,相信随便瞟几眼就能迅速过完整个知识,脑补回来。
下方即为我手绘的Android框架体系架构知识脑图,由于是xmind文件,不好上传,所以小编将其以图片形式导出来传在此处,细节方面不是特别清晰。但可给感兴趣的朋友提供完整的Android框架体系架构知识脑图原件(包括上方的面试解析xmind文档)
除此之外,前文所提及的Alibaba珍藏版 Android框架体系架构 手写文档以及一本 《大话数据结构》 书籍等等相关的学习笔记文档,也皆可分享给认可的朋友!
——感谢大家伙的认可支持,请注意:点赞+点赞+点赞!!!
项目、讲解视频,并且后续会持续更新**
如果你觉得这些内容对你有帮助,可以添加V:vip204888 备注Android获取(资料价值较高,非无偿)
[外链图片转存中…(img-yNA8hwW3-1711589304096)]
最后:学习总结——Android框架体系架构知识脑图(纯手绘xmind文档)
学完之后,若是想验收效果如何,其实最好的方法就是可自己去总结一下。比如我就会在学习完一个东西之后自己去手绘一份xmind文件的知识梳理大纲脑图,这样也可方便后续的复习,且都是自己的理解,相信随便瞟几眼就能迅速过完整个知识,脑补回来。
下方即为我手绘的Android框架体系架构知识脑图,由于是xmind文件,不好上传,所以小编将其以图片形式导出来传在此处,细节方面不是特别清晰。但可给感兴趣的朋友提供完整的Android框架体系架构知识脑图原件(包括上方的面试解析xmind文档)
[外链图片转存中…(img-9erXRlcs-1711589304096)]
除此之外,前文所提及的Alibaba珍藏版 Android框架体系架构 手写文档以及一本 《大话数据结构》 书籍等等相关的学习笔记文档,也皆可分享给认可的朋友!
——感谢大家伙的认可支持,请注意:点赞+点赞+点赞!!!