Contents
6. Adjust elevation
Shrineに合った特定の色とタイポグラフィでページのスタイルを設定したので、Shrineの製品を示すカードを見てみましょう。 現在、カードはサイトのナビゲーションの横にある白い表面に置かれています。
(1)Adjust Card elevation
home.dartで、Cardウィジェットのelevationプロパティの値を設定します。
elevation: 0.0,
カードの下の影を取り除きました。
ここまでのコード
//home.dart import 'package:flutter/material.dart'; import 'package:intl/intl.dart'; import 'model/products_repository.dart'; import 'model/product.dart'; class HomePage extends StatelessWidget { List<Card> _buildGridCards(BuildContext context) { List<Product> products = ProductsRepository.loadProducts(Category.all); if (products == null || products.isEmpty) { return const <Card>[]; } final ThemeData theme = Theme.of(context); final NumberFormat formatter = NumberFormat.simpleCurrency( locale: Localizations.localeOf(context).toString()); return products.map((product) { return Card( clipBehavior: Clip.hardEdge, elevation: 0.0, //←(1)Adjust Card elevationで追加。 child: Column( crossAxisAlignment: CrossAxisAlignment.center, children: <Widget>[ AspectRatio( aspectRatio: 18 / 11, child: Image.asset( product.assetName, package: product.assetPackage, fit: BoxFit.fitWidth, ), ), Expanded( child: Padding( padding: EdgeInsets.fromLTRB(16.0, 12.0, 16.0, 8.0), child: Column( mainAxisAlignment: MainAxisAlignment.end, crossAxisAlignment: CrossAxisAlignment.center, // TODO: Change innermost Column (103) children: <Widget>[ // TODO: Handle overflowing labels (103) Text( product == null ? '' : product.name, style: theme.textTheme.button, softWrap: false, overflow: TextOverflow.ellipsis, maxLines: 1, ), SizedBox(height: 4.0), Text( product == null ? '' : formatter.format(product.price), style: theme.textTheme.caption, ), ], ), ), ), ], ), ); }).toList(); } // TODO: Add a variable for Category (104) @override Widget build(BuildContext context) { // TODO: Return an AsymmetricView (104) // TODO: Pass Category variable to AsymmetricView (104) return Scaffold( appBar:AppBar( leading: IconButton( icon: Icon( Icons.menu, semanticLabel: 'menu', ), onPressed: () { print('Menu button'); }, ), title: Text('SHRINE'), actions: <Widget>[ IconButton( icon: Icon( Icons.search, semanticLabel: 'search', ), onPressed: () { print('Search button'); }, ), IconButton( icon: Icon( Icons.tune, semanticLabel: 'filter', ), onPressed: () { print('Filter button'); }, ), ], ), body: GridView.count( crossAxisCount: 2, padding: EdgeInsets.all(16.0), childAspectRatio: 8.0 / 9.0, // TODO: Build a grid of cards (102) children: _buildGridCards(context), ), resizeToAvoidBottomInset: false, ); } }
ログイン画面のコンポーネントの高さを変更して、それを補完しましょう。
RaisedButtonsのデフォルトの高さは2です。もっと高くしてみましょう。
login.dartで、elevationプロパティの値を[NEXT] RaisedButtonウィジェットに追加します。
RaisedButton(
child: Text('NEXT'),
elevation: 8.0, // ←追加する。
Hot restartしてみましょう。影が変化してボタンの高さが高くなりました。
ここまでのコード
//login.dart import 'package:flutter/material.dart'; class LoginPage extends StatefulWidget { @override _LoginPageState createState() => _LoginPageState(); } class _LoginPageState extends State<LoginPage> { final _usernameController = TextEditingController(); final _passwordController = TextEditingController(); final _unfocusedColor = Colors.grey[600]; final _usernameFocusNode = FocusNode(); final _passwordFocusNode = FocusNode(); @override void initState() { super.initState(); _usernameFocusNode.addListener(() { setState(() { //Redraw so that the username label reflects the focus state }); }); _passwordFocusNode.addListener(() { setState(() { //Redraw so that the password label reflects the focus state }); }); } @override Widget build(BuildContext context) { return Scaffold( body: SafeArea( child: ListView( padding: EdgeInsets.symmetric(horizontal: 24.0), children: <Widget>[ SizedBox(height: 80.0), Column( children: <Widget>[ Image.asset('assets/diamond.png'), SizedBox(height: 16.0), Text('SHRINE', style: Theme.of(context).textTheme.headline5, ), ], ), SizedBox(height: 120.0), // TODO: Wrap Username with AccentColorOverride (103) // TODO: Remove filled: true values (103) // TODO: Wrap Password with AccentColorOverride (103) TextField( controller:_usernameController, decoration: InputDecoration( fillColor:Colors.amber, //filled: true, labelText: 'Username', labelStyle: TextStyle( color: _usernameFocusNode.hasFocus ? Theme.of(context).accentColor : _unfocusedColor), ), focusNode: _usernameFocusNode, ), // spacer SizedBox(height: 12.0), // [Password] TextField( controller:_passwordController, decoration: InputDecoration( fillColor:Colors.lightBlueAccent, //filled: true, labelText: 'Password', labelStyle: TextStyle( color: _passwordFocusNode.hasFocus ? Theme.of(context).accentColor : _unfocusedColor), ), obscureText: true, ), ButtonBar( // TODO: Add a beveled rectangular border to CANCEL (103) children: <Widget>[ FlatButton( child: Text('CANCEL'), onPressed: () { _usernameController.clear(); _passwordController.clear(); }, ), // TODO: Add an elevation to NEXT (103) // TODO: Add a beveled rectangular border to NEXT (103) RaisedButton( child: Text('NEXT'), elevation: 8.0, //←(2)Change the elevation of the NEXT buttonで追加。 onPressed: () { Navigator.pop(context); }, ), ], ), ], ), ), ); } }
elevationに関する注記:
すべてのマテリアルデザインサーフェスとコンポーネントには、elevationがあります。
7. Add Shape
Shrineは、八角形または長方形の要素を定義する、クールな幾何学的スタイルを持っています。 ホーム画面のカードとログイン画面のテキストフィールドとボタンにその形状のスタイルを実装しましょう。
(1)Change the text field shapes on the login screen
app.dartで、特別なカットコーナーボーダーファイルをインポートします。
import 'supplemental/cut_corners_border.dart';
inputDecorationTheme: InputDecorationTheme(
focusedBorder: CutCornersBorder(
borderSide: BorderSide(
width: 2.0,
color: kShrineBrown900,
),
),
border: CutCornersBorder(), // Replace code
),
ここまでのコード
//app.dart(_buildShrineTheme()のみ) ThemeData _buildShrineTheme() { final ThemeData base = ThemeData.light(); return base.copyWith( accentColor: kShrineBrown900, primaryColor: kShrinePink100, buttonTheme: base.buttonTheme.copyWith( buttonColor: kShrinePink100, colorScheme: base.colorScheme.copyWith( secondary: kShrineBrown900, ), ), buttonBarTheme: base.buttonBarTheme.copyWith( buttonTextTheme: ButtonTextTheme.accent, ), scaffoldBackgroundColor: kShrineBackgroundWhite, cardColor: kShrineBackgroundWhite, textSelectionColor: kShrinePink100, errorColor: kShrineErrorRed, textTheme: _buildShrineTextTheme(base.textTheme), primaryTextTheme: _buildShrineTextTheme(base.primaryTextTheme), accentTextTheme: _buildShrineTextTheme(base.accentTextTheme), primaryIconTheme: base.iconTheme.copyWith( color: kShrineBrown900 ), //↓(1)Change the text field shapes on the login screenで変更。 //OutLineInputBorderをCutCornersBorderに変更する。 inputDecorationTheme: InputDecorationTheme( focusedBorder: CutCornersBorder( //←ここを変更 borderSide: BorderSide( width: 2.0, color: kShrineBrown900, ), ), border: CutCornersBorder(), //←ここを変更 ), ); }
↓変更前のボタン
login.dartで、[キャンセル(CANCEL)]ボタンに面取りされた長方形の境界線を追加します。
FlatButton(
child: Text('CANCEL'),
shape: BeveledRectangleBorder(
borderRadius: BorderRadius.all(Radius.circular(7.0)),
),
↓変更後のボタン
ここまでのコード
FlatButtonには目に見える形状がないのに、なぜ境界線の形状を追加するのですか? ripple(波紋)アニメーションは、タッチすると同じ形状(角が取れた形)にバインドされます。
次に、同じ形状を[NEXT]ボタンに追加します。
RaisedButton(
child: Text('NEXT'),
elevation: 8.0,
shape: BeveledRectangleBorder(
borderRadius: BorderRadius.all(Radius.circular(7.0)),
),
すべてのボタンの形状を変更するには、app.dartのbuttonThemeを更新することもできます。 それは学習者への挑戦として残されています!
ここまでのコード
//login.dart(ButtonBar部分のみ) ButtonBar( // TODO: Add a beveled rectangular border to CANCEL (103) children: <Widget>[ FlatButton( child: Text('CANCEL'), //↓(2)Change button shapes on the login screenで追加。 shape: BeveledRectangleBorder( borderRadius: BorderRadius.all(Radius.circular(7.0)), ), onPressed: () { _usernameController.clear(); _passwordController.clear(); }, ), // TODO: Add an elevation to NEXT (103) // TODO: Add a beveled rectangular border to NEXT (103) RaisedButton( child: Text('NEXT'), elevation: 8.0, //↓(2)Change button shapes on the login screenで追加。 shape: BeveledRectangleBorder( borderRadius: BorderRadius.all(Radius.circular(7.0)), ), onPressed: () { Navigator.pop(context); }, ), ], ),
NEXTボタンも角が取れました。
8. Change the layout
次に、レイアウトを変更して、さまざまなアスペクト比とサイズでカードを表示し、各カードが他のカードとは異なるように見えるようにします。
(1)Replace GridView with AsymmetricView
非対称レイアウトのファイルはすでに作成済みです。
home.dartで、ファイル全体を次のように変更します。
import 'package:flutter/material.dart';
import 'model/products_repository.dart';
import 'model/product.dart';
import 'supplemental/asymmetric_view.dart';
class HomePage extends StatelessWidget {
// TODO: Add a variable for Category (104)
@override
Widget build(BuildContext context) {
// TODO: Return an AsymmetricView (104)
// TODO: Pass Category variable to AsymmetricView (104)
return Scaffold(
appBar: AppBar(
brightness: Brightness.light,
leading: IconButton(
icon: Icon(Icons.menu),
onPressed: () {
print('Menu button');
},
),
title: Text('SHRINE'),
actions: <Widget>[
IconButton(
icon: Icon(Icons.search),
onPressed: () {
print('Search button');
},
),
IconButton(
icon: Icon(Icons.tune),
onPressed: () {
print('Filter button');
},
),
],
),
body: AsymmetricView(products: ProductsRepository.loadProducts(Category.all)),
);
}
}
現在、製品はwoven-inspired patternで水平方向にスクロールします。 また、ステータスバーのテキスト(上部の時間とネットワーク)が黒になりました。 これは、AppBarの明るさをlight、brightness.lightに変更したためです。
9. Try another theme
色はブランドを表現するための強力な方法であり、色のわずかな変化がユーザーエクスペリエンスに大きな影響を与える可能性があります。 これをテストするために、ブランドの配色が完全に異なっている場合、Shrineがどのように見えるかを見てみましょう。
(1)Modify colors
colors.dartに、以下を追加します。
const kShrinePurple = Color(0xFF5D1049);
const kShrineBlack = Color(0xFF000000);
app.dartで、_buildShrineTheme()関数と_buildShrineTextTheme関数を次のように変更します。
ThemeData _buildShrineTheme() {
final ThemeData base = ThemeData.light();
return base.copyWith(
primaryColor: kShrinePurple,
buttonTheme: base.buttonTheme.copyWith(
buttonColor: kShrinePurple,
textTheme: ButtonTextTheme.primary,
colorScheme: ColorScheme.light().copyWith(primary: kShrinePurple)
),
scaffoldBackgroundColor: kShrineSurfaceWhite,
textTheme: _buildShrineTextTheme(base.textTheme),
primaryTextTheme: _buildShrineTextTheme(base.primaryTextTheme),
accentTextTheme: _buildShrineTextTheme(base.accentTextTheme),
primaryIconTheme: base.iconTheme.copyWith(
color: kShrineSurfaceWhite
),
inputDecorationTheme: InputDecorationTheme(
focusedBorder: CutCornersBorder(
borderSide: BorderSide(
width: 2.0,
color: kShrinePurple,
),
),
border: CutCornersBorder(),
),
);
}
TextTheme _buildShrineTextTheme(TextTheme base) {
return base.copyWith(
headline5: base.headline5.copyWith(
fontWeight: FontWeight.w500,
),
headline6: base.headline6.copyWith(
fontSize: 18.0,
),
caption: base.caption.copyWith(
fontWeight: FontWeight.w400,
fontSize: 14.0,
),
bodyText1: base.bodyText1.copyWith(
fontWeight: FontWeight.w500,
fontSize: 16.0,
),
).apply(
fontFamily: 'Rubik',
);
}
login.dartで、ロゴのダイアンモンドを黒に着色します。
Image.asset(
'assets/diamond.png',
color: kShrineBlack, // ←を追加
),
上記のようにコードを追加した時エラー(赤い波下線)が出た場合、
login.dartがcolors.dartをインポート(import)していないためエラーが出ています。
login.dartの最初で
import 'colors.dart';
を記述すればエラーは消えます。これを自分で記述してもいいのですが、
ファイル読み込み(import文は)以下の方法で解決できます。
↓
赤い波下線が出ている部分にカーソルを持ってきて
option+enterキー
を押すと、下図のようにエラーを解決するための選択肢が提示されます。
今回import文が必要ですので一番上の選択肢を選択します。
すると自動的に
import ‘colors.dart’;
を記述してくれます。これで解決ですので自分で記述する必要はありません。
わかりにくいですが(笑)ダイヤモンドの色が黒に変化しました。
ここまでのコード
//login.dart import 'package:flutter/material.dart'; import 'colors.dart'; class LoginPage extends StatefulWidget { @override _LoginPageState createState() => _LoginPageState(); } class _LoginPageState extends State<LoginPage> { final _usernameController = TextEditingController(); final _passwordController = TextEditingController(); final _unfocusedColor = Colors.grey[600]; final _usernameFocusNode = FocusNode(); final _passwordFocusNode = FocusNode(); @override void initState() { super.initState(); _usernameFocusNode.addListener(() { setState(() { //Redraw so that the username label reflects the focus state }); }); _passwordFocusNode.addListener(() { setState(() { //Redraw so that the password label reflects the focus state }); }); } @override Widget build(BuildContext context) { return Scaffold( body: SafeArea( child: ListView( padding: EdgeInsets.symmetric(horizontal: 24.0), children: <Widget>[ SizedBox(height: 80.0), Column( children: <Widget>[ Image.asset('assets/diamond.png', color: kShrineBlack, //←(1)Modify colorsで追加。 ), SizedBox(height: 16.0), Text('SHRINE', style: Theme.of(context).textTheme.headline5, ), ], ), SizedBox(height: 120.0), ....
home.dartで、AppBarの明るさを暗くします。
brightness: Brightness.dark,
AppBarの色が紫に変わったので、アプリの感じが変わりましたね。
ここまでのコード
//home.dart import 'package:flutter/material.dart'; import 'model/products_repository.dart'; import 'model/product.dart'; import 'supplemental/asymmetric_view.dart'; class HomePage extends StatelessWidget { // TODO: Add a variable for Category (104) @override Widget build(BuildContext context) { // TODO: Return an AsymmetricView (104) // TODO: Pass Category variable to AsymmetricView (104) return Scaffold( appBar: AppBar( brightness: Brightness.dark, //←(1)Modify colorsで変更 leading: IconButton( icon: Icon(Icons.menu), onPressed: () { print('Menu button'); }, ), ...
10. Recap(まとめ)
デザイナーの設計仕様に似たアプリを作成できましたね。
ここまで、Theme、typography、elevation、およびshapeのMDCコンポーネントを使用しました。 MDC-Flutterライブラリでさらに多くのコンポーネントとサブシステムを探索できます。
supplementalディレクトリのファイルを掘り下げて、水平スクロールの非対称レイアウトグリッドを作成した方法を見てみてください。
計画しているアプリのデザインに、MDCライブラリにコンポーネントがない要素が含まれている場合はどうすれば良いでしょう?
MDC-104:Material Design Advanced Componentsでは、MDCライブラリを使用してカスタムコンポーネントを作成し、特定の外観を実現する方法を示します。
参考
https://codelabs.developers.google.com/codelabs/mdc-103-flutter#5