flutter 教程
参阅地址:
codelabs.developers.google.com/codelabs/fl…
dartpad.dev/?id=e7076b4…
代码精读
Tips:本代码来源于参阅地址中的DartPad
main
其他关于flutter的不太了解,仅仅单纯看这个教程觉得全体语法上较好了解,widget is all you need
。
首要最开始是一个主函数进口,这儿需求履行一个runApp
的函数,里面放的是你自己的界说的app
,当然也是一个widget
。
void main() {
runApp(MyApp());
}
MyApp
第2行是承继了StatelessWidget的一切的key(详情参阅下面或此文),意图是为了削减代码量。
To avoid having to manually pass each parameter into the super invocation of a constructor, you can use super-initializer parameters to forward parameters to the specified or default superclass constructor.
为了防止手动将每个参数传递给结构函数的超级调用,您能够运用超级初始化器参数将参数转发到指定或默许的超类结构函数。
从第5行到17行都是在重写Widget
的build
的办法,这是每一个承继StatelessWidget
类都需求写的。
build
的回来类型决定了这个类的功用,当时这个是ChangeNotifierProvider
,是Flutter状况管理的一部分,能够在app
的不同界面中同享使用程序的状况,这儿运用的是ChangeNotifier
,就是下面界说的MyAppState
。
这儿插入两个小概念:
- 短时状况:短时状况(有时也称用户界面 (UI) 状况或者局部状况)是你能够完全包含在一个独立 widget 中的状况。
- 使用状况:假如你想在你的使用中的多个部分之间同享一个非短时的状况,并且在用户会话期间保留这个状况,我们称之为使用状况(有时也称同享状况)。
简而言之:短时状况只存在于statefulwidget里,而使用状况则能够被在使用进程被多个部分拜访,需求长期保留。
具体了解能够检查Flutter中文。
class MyApp extends StatelessWidget {
const MyApp({super.key}); // 承继一切的key
@override
Widget build(BuildContext context) {
return ChangeNotifierProvider(
create: (context) => MyAppState(),
child: MaterialApp(
title: 'Namer App',
theme: ThemeData(
useMaterial3: true,
colorScheme: ColorScheme.fromSeed(seedColor: Colors.deepOrange),
),
home: MyHomePage(),
),
);
}
}
MyAppState
这儿界说了需求被同享的信息,在这个项目中是历史记录,当时的单词,喜爱的词表。界说的函数里面加入了notifyListeners
意味着当时的这次变更需求告诉一切的监听者。
class MyAppState extends ChangeNotifier {
var current = WordPair.random();
var history = <WordPair>[];
GlobalKey? historyListKey;
void getNext() {
history.insert(0, current);
var animatedList = historyListKey?.currentState as AnimatedListState?;
animatedList?.insertItem(0);
current = WordPair.random();
notifyListeners();
}
var favorites = <WordPair>[];
void toggleFavorite([WordPair? pair]) {
pair = pair ?? current;
if (favorites.contains(pair)) {
favorites.remove(pair);
} else {
favorites.add(pair);
}
notifyListeners();
}
void removeFavorite(WordPair pair) {
favorites.remove(pair);
notifyListeners();
}
}
MyHomePage
这一部分的代码比较长,注释介绍了大约的细节,比较简单。
class MyHomePage extends StatefulWidget {
@override
State<MyHomePage> createState() => _MyHomePageState();
}
class _MyHomePageState extends State<MyHomePage> {
var selectedIndex = 0;
@override
Widget build(BuildContext context) {
var colorScheme = Theme.of(context).colorScheme; // 界说颜色主题为系统
Widget page; // 界说界面,经过seleteIndex的值来进行挑选,这儿默许为GeneratorPage
switch (selectedIndex) {
case 0:
page = GeneratorPage();
break;
case 1:
page = FavoritesPage();
break;
default:
throw UnimplementedError('no widget for $selectedIndex');
}
// The container for the current page, with its background color
// and subtle switching animation.
var mainArea = ColoredBox(
color: colorScheme.surfaceVariant,
child: AnimatedSwitcher( // 这儿是动画切换,主要是左面的导航栏
duration: Duration(milliseconds: 200),
child: page,
),
);
return Scaffold(
body: LayoutBuilder(
builder: (context, constraints) {
if (constraints.maxWidth < 450) {
// Use a more mobile-friendly layout with BottomNavigationBar
// on narrow screens.
return Column(
children: [
Expanded(child: mainArea),
SafeArea(
child: BottomNavigationBar(
items: [
BottomNavigationBarItem(
icon: Icon(Icons.home),
label: 'Home',
),
BottomNavigationBarItem(
icon: Icon(Icons.favorite),
label: 'Favorites',
),
],
currentIndex: selectedIndex,
onTap: (value) { // 依据当时的位置挑选界面
setState(() {
selectedIndex = value;
});
},
),
)
],
);
} else { // 大屏下面的屏幕自适应
return Row(
children: [
SafeArea(
child: NavigationRail(
extended: constraints.maxWidth >= 600,
destinations: [
NavigationRailDestination(
icon: Icon(Icons.home),
label: Text('Home'),
),
NavigationRailDestination(
icon: Icon(Icons.favorite),
label: Text('Favorites'),
),
],
selectedIndex: selectedIndex,
onDestinationSelected: (value) {
setState(() {
selectedIndex = value;
});
},
),
),
Expanded(child: mainArea),
],
);
}
},
),
);
}
}
这儿界说了一个具有短时状况的homePage,主要是因为我们有一个主界面切换的需求,需求对seletedIndex进行监听。
class MyHomePage extends StatefulWidget {
@override
State<MyHomePage> createState() => _MyHomePageState();
}
GeneratorPage
class GeneratorPage extends StatelessWidget {
@override
Widget build(BuildContext context) {
var appState = context.watch<MyAppState>();
var pair = appState.current;
IconData icon; // 判别当时词是否是favorites中的,假如是的话就替换icon
if (appState.favorites.contains(pair)) {
icon = Icons.favorite;
} else {
icon = Icons.favorite_border;
}
return Center(
child: Column( // 从上到下,排列children
mainAxisAlignment: MainAxisAlignment.center,
children: [
Expanded( // 1. 显现HistoryListView()
flex: 3,
child: HistoryListView(),
),
SizedBox(height: 10),
BigCard(pair: pair), // 2. 显现BigCard
SizedBox(height: 10),
Row( // 3. 横线显现
mainAxisSize: MainAxisSize.min,
children: [
ElevatedButton.icon( // 3.1 like 按钮
onPressed: () {
appState.toggleFavorite();
},
icon: Icon(icon),
label: Text('Like'),
),
SizedBox(width: 10),
ElevatedButton( // 3.2 next 按钮
onPressed: () {
appState.getNext();
},
child: Text('Next'),
),
],
),
Spacer(flex: 2),
],
),
);
}
}
BigCard
class BigCard extends StatelessWidget {
const BigCard({
Key? key,
required this.pair,
}) : super(key: key);
final WordPair pair;
@override
Widget build(BuildContext context) {
var theme = Theme.of(context);
var style = theme.textTheme.displayMedium!.copyWith(
color: theme.colorScheme.onPrimary,
);
return Card( // 回来的是卡片类型
color: theme.colorScheme.primary,
child: Padding(
padding: const EdgeInsets.all(20),
child: AnimatedSize(
duration: Duration(milliseconds: 200),
// Make sure that the compound word wraps correctly when the window
// is too narrow.
child: MergeSemantics(
child: Wrap(
children: [
Text( // 这儿将单词拆分成了两个部分,增强了体现效果。
pair.first,
style: style.copyWith(fontWeight: FontWeight.w200),
),
Text(
pair.second,
style: style.copyWith(fontWeight: FontWeight.bold),
)
],
),
),
),
),
);
}
}
FavoritesPage
class FavoritesPage extends StatelessWidget {
@override
Widget build(BuildContext context) {
var theme = Theme.of(context);
var appState = context.watch<MyAppState>();
if (appState.favorites.isEmpty) { // 判别favorites是否有内容
return Center(
child: Text('No favorites yet.'),
);
}
return Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Padding(
padding: const EdgeInsets.all(30),
child: Text('You have '
'${appState.favorites.length} favorites:'),
),
Expanded(
// Make better use of wide windows with a grid.
child: GridView(
gridDelegate: SliverGridDelegateWithMaxCrossAxisExtent( //组件参数
maxCrossAxisExtent: 400,
childAspectRatio: 400 / 80,
),
children: [
for (var pair in appState.favorites)
ListTile(
leading: IconButton(
icon: Icon(Icons.delete_outline, semanticLabel: 'Delete'),
color: theme.colorScheme.primary,
onPressed: () {
appState.removeFavorite(pair);//删去办法
},
),
title: Text(
pair.asLowerCase,
semanticsLabel: pair.asPascalCase,
),
),
],
),
),
],
);
}
}
HistoryListView
class _HistoryListViewState extends State<HistoryListView> {
/// Needed so that [MyAppState] can tell [AnimatedList] below to animate
/// new items.
final _key = GlobalKey();
/// 这儿运用了梯度进行渲染
/// Used to "fade out" the history items at the top, to suggest continuation.
static const Gradient _maskingGradient = LinearGradient(
// This gradient goes from fully transparent to fully opaque black...
colors: [Colors.transparent, Colors.black],
// ... from the top (transparent) to half (0.5) of the way to the bottom.
stops: [0.0, 0.5],
begin: Alignment.topCenter,
end: Alignment.bottomCenter,
);
@override
Widget build(BuildContext context) {
final appState = context.watch<MyAppState>();
appState.historyListKey = _key;
return ShaderMask(
shaderCallback: (bounds) => _maskingGradient.createShader(bounds),
// This blend mode takes the opacity of the shader (i.e. our gradient)
// and applies it to the destination (i.e. our animated list).
blendMode: BlendMode.dstIn,
child: AnimatedList(
key: _key,
reverse: true,
padding: EdgeInsets.only(top: 100),
initialItemCount: appState.history.length,
itemBuilder: (context, index, animation) {
final pair = appState.history[index];
return SizeTransition(
sizeFactor: animation,
child: Center(
child: TextButton.icon(
onPressed: () {
appState.toggleFavorite(pair);
},
icon: appState.favorites.contains(pair)
? Icon(Icons.favorite, size: 12)
: SizedBox(),
label: Text(
pair.asLowerCase,
semanticsLabel: pair.asPascalCase,
),
),
),
);
},
),
);
}
}
总结
全体的代码复现和了解其中的原理大约需求2~3小时。期望各位小伙伴耐性学习,接下来将自己做了一个TODO-LIST,毕竟写项目是最好的学习方法,之后写完会持续分享一下学习经验~