もっぺんプログラミング(´・ω・`)

もっぺん頑張って副業プログラマを目指してます。

Flutter動画やってみよ(eBook)4

以下のYoutubeの動画を、実際になぞっていきます。

前回は、こちら。

Flutter動画やってみよ(eBook)3 - もっぺんプログラミング(´・ω・`)

ベスト商品の掲載枠の追加

今日のベスト商品(Best of the day)を表示するエリアを追加していきます。

先程のSingleChildScrollView (商品を横スクロールさせていたWidget)の下に追加していきます。

), // ← SingleChildScrollViewの閉じカッコ
Padding(
  padding: EdgeInsets.symmetric(horizontal: 24), 
  child: Column(
    crossAxisAlignment: CrossAxisAlignment.start,
    children: <Widget>[
      RichText(
        text: TextSpan(
          style: Theme.of(context).textTheme.display1,
          children: [
            TextSpan(text: "Best of the "),
            TextSpan(
              text: "day",
              style: TextStyle(fontWeight: FontWeight.bold),
            ),
          ],
        ),
      ),
      Container(
        margin: EdgeInsets.symmetric(vertical: 20),
        width: double.infinity,
        height: 205,
        child: Stack(
          children: <Widget>[
            Positioned(
              bottom: 0, 
              left: 0,
              child: Container(
                height: 185, 
                width: double.infinity,
                decoration: BoxDecoration(
                  color: Color(0xFFEAEAEA).withOpacity(.45),
                  borderRadius: BorderRadius.circular(29),
                ),
              ),
            ),
            Positioned(
              right: 0,
              top: 0,
              child: Image.asset("assets/images/book-3.png"),
              width: size.width * .37,
            ),
          ],
        ),
      ),
    ],
  )
)

これで、こんな感じのエリアが追加されます。
(Best of the day からのエリア)

f:id:momoizo:20200531022254p:plain:w300

上下段レイアウトのために、Columnを追加しています。
タイトルはRichTextで表現し、本の画像を置いているグレーのエリアは、Containerを使っています。

このコンテナ内に、本の説明文・お気に入りボタン・評価を追加していきます。

まず、テキストの表示箇所をコンテナの少し内側になるように、コンテナ内にPaddingをセットします。

child: Container(
  padding: EdgeInsets.only(
    left: 24,
    top: 24,
    right: size.width * .35,
  ),

次に、コンテナのBoxDecoration の閉じタグ以降に以下を追加します。

), // ← BoxDecoration の閉じタグ
child: Column(
  crossAxisAlignment: CrossAxisAlignment.start,
  children: <Widget>[
    Text(
      "New York Time Best For 11th March 2020",
      style: TextStyle(
        fontSize: 9,
        color: kLightBlackColor,
      ),
    ),
    SizedBox(height: 5),
    Text(
      "How To Win \nFriends & Influence",
      //style: Theme.of(context).textTheme.title, // ← ビデオのコードはDuplicatedだったため、書き直しました。
      style: TextStyle(
      fontSize: 16,
      fontWeight: FontWeight.bold,
    ),
    Text(
      "Gary Venchuk",
      style: TextStyle(color: kLightBlackColor),
    ),
    SizedBox(height: 10),
    Row(
      children: <Widget>[],
    )
  ],
)

f:id:momoizo:20200531030701p:plain:w300

テキストの下にレーティングを追加します。
レーティングは、BookRating としてWigetに切り出しているので、それを使います。

説明テキストも合わせて以下のようなコードです。

Row(
  children: <Widget>[
    BookRating(score: 4.8), // ← BookRatingを追加
    SizedBox(width: 10),
    Expanded(
      child: Text(
        "When the earth was flat and everyone wanted to win the game of the best and people…",
        maxLines: 3,
        overflow: TextOverflow.ellipsis,
        style: TextStyle(
          fontSize: 10,
          color: kLightBlackColor,
        )
      )
    )
  ],
)

f:id:momoizo:20200531032432p:plain:w300

こんな感じになりました。
最後に、”Read”ボタンを追加します。

Positioned(
  right: 0,
  top: 0,
  child: Image.asset("assets/images/book-3.png"),
  width: size.width * .37,
),
Positioned( // ← ボタン追加
  bottom: 0,
  right: 0,
  child: SizedBox(
    height: 40,
    width: size.width * .3,
    child: TwoSideRoundedButton(
      text: "Read",
      radious: 24,
      press: () {},
    )
  )
)

f:id:momoizo:20200531033924p:plain:w300

最後に、コードの読みやすさのために Container を別メソッドに切り出します。
動画だと、ショートカットから「Extract Method」を選択して実行しています。

が、、、これが自分の環境だと出てこず…
手動で、メソッドに変換しました。

↓以下のような感じです。

                  ), // ← RichText の閉じタグ
                  bestOfTheDayCard(size, context), // ← もともと、Containerを記載していた箇所。
                ],
              ),
            ),
          ],
        ),
      ),
    );
  }
}

// ↓ 切り出したメソッド。return の後ろに、Container のコードを貼り付けます。
Container bestOfTheDayCard(Size size, BuildContext context) {
  return Container(

次に、この下にオススメ欄を追加していきます。
つづきます。

たとえる技術

たとえる技術

  • 作者:せきしろ
  • 発売日: 2016/10/12
  • メディア: 単行本(ソフトカバー)

Flutter動画やってみよ(eBook)3

以下のYoutubeの動画を、実際になぞっていきます。

前回は、こちら。

Flutter動画やってみよ(eBook)その2 - もっぺんプログラミング(´・ω・`)

本のタイトルを付ける

前の Positioned の次に、新たにPositionedを追加します。

), // Positioned 
Positioned(
  top: 160,
  child: Container(
    height: 85,
    width: 202,
    child: Column(
      crossAxisAlignment: CrossAxisAlignment.start,
      children: <Widget>[
        Padding(
          padding: EdgeInsets.only(left: 24),
          child: RichText(
            text: TextSpan(
              style: TextStyle(color: kBlackColor),
              children: [
                TextSpan(
                  text: "Crushing & Influence\n",
                  style: TextStyle(
                    fontWeight: FontWeight.bold,
                  ),
                ),
                TextSpan(
                  text: "Gery Venchuk",
                  style: TextStyle(
                    color: kLightBlackColor,
                  ),
                ),
              ],
            ),
          ),
        )
      ],
    ),
  ),
),

f:id:momoizo:20200525013042p:plain:w300

詳細表示用のボタンを追加する。

上記のPaddingの閉じタグから、以下を追加します。

), // Padding
Spacer(),
Row(
  children: <Widget>[
    Container(
      width: 101,
      padding: EdgeInsets.symmetric(vertical: 10),
      alignment: Alignment.center,
      child: Text("Details")
    ),
    Expanded(
      child: Container(
        alignment: Alignment.center,
        padding: EdgeInsets.symmetric(vertical: 10),
        decoration: BoxDecoration(
          color: kBlackColor,
          borderRadius: BorderRadius.only(
            topLeft: Radius.circular(29),
            bottomRight: Radius.circular(29),
          ),
        ),
        child: Text(
          "Read",
          style: TextStyle(color: Colors.white),
        ),
      ),
    ),
  ],
)

f:id:momoizo:20200525013120p:plain:w300

こんな感じになりました。

書籍ごとに、再利用したいので Container 以下をWidgetとして切り出します。
切り出したWidgetで、マウス操作を補足したいので GestureDetector で囲みます。

また、textや、ボタンを押した際の動作を外部から指定できるように
コンストラクタの変数として追加します。

class TwoSideRoundedButton extends StatelessWidget {
  final String text;  // ← 追加
  final double radious;  // ← 追加
  final Function press;  // ← 追加
  const TwoSideRoundedButton({
    Key key,  // ← 追加
    this.text,  // ← 追加
    this.radious = 29,  // ← 追加
    this.press,
  }) : super(key: key);

  @override
  Widget build(BuildContext context) {
    return GestureDetector(
      onTap: press, // ← 変数を指定
      child: Container(
        alignment: Alignment.center,
        padding: EdgeInsets.symmetric(vertical: 10),
        decoration: BoxDecoration(
          color: kBlackColor,
          borderRadius: BorderRadius.only(
            topLeft: Radius.circular(radious),  // ← 変数に置き換え
            bottomRight: Radius.circular(radious),
          ),
        ),
        child: Text(
          text,  // ← 変数に置き換え
          style: TextStyle(color: Colors.white),
        ),
      ),
    );
  }
}

呼び出し側の引数は以下のように指定します。

Expanded(
  child: TwoSideRoundedButton( 
    text: "read", // ← 
    press: () {},
  ),
),

最後にTwoSideRoundedButton クラスを、 lib/widgets 以下に two_side_rounded_button.dart に切り出します。

本のカードをリスト化する

1つの本のカードが出来ました。 ここでは、本のカードをWidgetとして切り出して、複数の書籍のリストを作成します。

書籍カードを作成したContainerを、別Widgetに切り出します。 名前は、ReadingListCard とします。

            SizedBox(height: 30),
            ReadingListCard(    // ← ここ ReadingListCard Widgetに置き換えています。
              image: "assets/images/book-1.png",
              title: "Crushing & Influence",
              auth: "Gary Venchuk",
              rating: 4.9,
            )
          ],
        ),
      ),
    );
  }
}

class ReadingListCard extends StatelessWidget {
  final String image;
  final String title;
  final String auth;
  final double rating;
  final Function pressDetails;
  final Function pressRead;

  const ReadingListCard({
    Key key,
    this.image,
    this.title,
    this.auth,
    this.rating,
    this.pressDetails,
    this.pressRead,
  }) : super(key: key);

  @override
  Widget build(BuildContext context) {
    return Container(
      margin: EdgeInsets.only(left: 24, bottom: 40),
      height: 245,
      width: 202,
      child: Stack(
        children: <Widget>[
        ....
        ],
      ),
    );
  }
}

本のカードを横に並べる

本のカードを横に並べたいので、Row を使って横に並べます。
さらに横スクロールで表示するために、SingleChildScrollView で囲みます。

以下のように修正します。

SizedBox(height: 30),
SingleChildScrollView(
  scrollDirection: Axis.horizontal,
  child: Row(
    children: <Widget>[
      ReadingListCard(
        image: "assets/images/book-1.png",
        title: "Crushing & Influence",
        auth: "Gary Venchuk",
        rating: 4.9,
      ),
      ReadingListCard(
        image: "assets/images/book-2.png",
        title: "Top Ten Business Hacks",
        auth: "Herman Joel",
        rating: 4.2,
      ),
    ],
  ),
),

こんな感じになりました。

f:id:momoizo:20200525031937p:plain:w300

つづきます。

Flutter動画やってみよ(eBook)その2

以下のYoutubeの動画を、実際になぞっていきます。

前回は、こちら。
Flutter動画やってみよ(eBook)その1 - もっぺんプログラミング(´・ω・`)

次のページを作る

前回、トップページに "start reading" ボタンを作ったので、その遷移先のページを用意していきます。

/lib/screens というディレクトリを作成し、そこに、home_screen.dart ファイルを作成します。
まずは、空の中身で作ります。

import 'package:flutter/material.dart';

class HomeScreen extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      body: Container(),
    );
  }
}

main.dart に戻って、ボタンを押したらHomeScreenに遷移するようにします。

child: RoundedButton(
    text: "start reading.",
    fontSize: 20,
    press: () {  // ← press の関数を追加
      Navigator.push(
        context,
        MaterialPageRoute(
          builder: (context) {
            return HomeScreen();
          },
        ),
      );
    }),

エミュレーターで画面遷移することが確認できたら、HomeScreenを作っていきましょう。

HomeScreen に背景とタイトルを付ける

背景画像とタイトルを設定します。
HomeScreen を以下のように編集します。

import 'package:flutter/material.dart';

class HomeScreen extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    var size = MediaQuery.of(context).size;
    return Scaffold(
      body: Container(
        width: double.infinity,
        decoration: BoxDecoration(
          image: DecorationImage(
            image: AssetImage("assets/images/main_page_bg.png"),
            alignment: Alignment.topCenter,
            fit: BoxFit.fitWidth,
          ),
        ),
        child: Column(
          crossAxisAlignment: CrossAxisAlignment.start,
          children: <Widget>[
            SizedBox(height: size.height * .1),
            Padding(
              padding: const EdgeInsets.symmetric(horizontal: 24),
              child: RichText(
                text: TextSpan(
                  style: Theme.of(context).textTheme.display1,
                  children: [
                    TextSpan(text: "what are you \nreading "),
                    TextSpan(
                        text: "today?",
                        style: TextStyle(
                          fontWeight: FontWeight.bold,
                        ))
                  ],
                ), //TextSpan
              ), // RichText
            ) // Padding
          ],
        ),
      ),
    );
  }
}

こんな感じになってます。

f:id:momoizo:20200524005801p:plain:w300

書籍の画像とお気に入りアイコンの追加

上記コードの最後のPaddingの後に、書籍画像とお気に入りアイコンを追加していきます。

), //上記の Paddingの終わり
SizedBox(height: 30),  // 余白を SizedBoxで作成。
Container(
  height: 245,
  width: 202,
  child: Stack(
    children: <Widget>[
      Positioned(
        bottom: 0,
        left: 0,
        right: 0,
        child: Container(
          height: 221,
          decoration: BoxDecoration(
            color: Colors.white,
            borderRadius: BorderRadius.circular(29),
            boxShadow: [
              BoxShadow(
                offset: Offset(0, 10),
                blurRadius: 33,
                color: kShadowColor,
              )
            ],
          ),
        ),
      ),
      Image.asset(
        "assets/images/book-1.png",
        width: 150,
      ),
      Positioned(
          top: 35,
          right: 10,
          child: Column(
            children: <Widget>[
              IconButton(
                icon: Icon(Icons.favorite_border),
                onPressed: () {},
              ), // IconButton
            ],
          ),
       ),
    ],
  ),
)

f:id:momoizo:20200524014800p:plain:w400

スターとレーティングの追加

書籍のお気に入りボタンの下に、スターとレーティングを追加していきます。
お気に入りアイコンの IconButton の下に続けてContainerを置いていきます。

), // IconButton
Container(
  padding:
      EdgeInsets.symmetric(vertical: 8, horizontal: 6),
  decoration: BoxDecoration(
    color: Colors.white,
    borderRadius: BorderRadius.circular(16),
    boxShadow: [
      BoxShadow(
        offset: Offset(3, 7),
        blurRadius: 20,
        color: Color(0xFd3D3D3).withOpacity(.5),
      ),
    ],
  ),
  child: Column(
    children: <Widget>[
      Icon(
        Icons.star,
        color: kIconColor,
        size: 15,
      ),
      SizedBox(height: 5),
      Text(
        "4.9",
        style: TextStyle(
          fontSize: 12,
          fontWeight: FontWeight.bold,
        ),
      ),
    ],
  ),
),

こんな感じになりました。

f:id:momoizo:20200524021621p:plain:w400

クラスを切り出す。

追加した、レーティングのContainerを別クラスに切り出します。
Extract Widget でサクッと切り出します。

f:id:momoizo:20200524023503p:plain:w400

スコアをコンストラクタのパラメーターに変更します。
Textの表示も、score 変数を表示するように変更しましょう。

class BookRating extends StatelessWidget {
  final double score;
  const BookRating({
    Key key,
    this.score,
  }) : super(key: key);

...
          Text(
            "$score",

あとは、呼び出し側でスコアを指定すれば完成です。

children: <Widget>[
  IconButton(
    icon: Icon(Icons.favorite_border),
    onPressed: () {},
  ),
  BookRating(score: 4.9),  // ← 4.9 を指定
],

画面の見た目は同じですが、コードがスッキリしました。

最後に、lib/widgets 以下に book_rating.dart ファイルを作成し、BookRatingクラスを移動しておきます。
パッケージの import を整備したら完了です。

続きます。

Flutter動画やってみよ(eBook)1

以下のYoutubeの動画を、実際になぞっていきます。

画面遷移とアニメーションが魅力的なアプリです。
こんなアプリをサクサク作れるようになりたい!

今回もVisual Studio Codeで新規プロジェクトを作成して初めて行きます。

では、行きましょう。

アセットの設定をする

画像を扱うために定番操作です。

assets/images ディレクトリを作ります。

次に、pubspec.yaml に、assets のディレクトリとして登録します。

flutter:
  assets:
    - assets/images/

画像ファイルは、GitHubから取得しておきます。

eBook_app_Flutter/assets/images at master · abuanwar072/eBook_app_Flutter · GitHub

定数用ファイル作成

lib/constants.dart を作成します。

import 'package:flutter/material.dart';

const kBlackColor = Color(0xFF393939);
const kLightBlackColor = Color(0xFF8F8F8F);
const kIconColor = Color(0xFFF48A37);
const kProgressIndicator = Color(0xFFBE7066);

final kShadowColor = Color(0xFFD3D3D3).withOpacity(.84);

main.dart を編集していく

初期の main.dart の内容を削除して、以下からスタートします。
※ MyHomePage を削除後、MyApp を少し編集で大丈夫です。

import 'package:flutter/material.dart';

void main() {
  runApp(MyApp());
}

class MyApp extends StatelessWidget {
  // This widget is the root of your application.
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      debugShowCheckedModeBanner: true, // ← 追加
      title: 'eBook App',
      theme: ThemeData(
        scaffoldBackgroundColor: Colors.white, // ← 追加
        primarySwatch: Colors.blue,
        visualDensity: VisualDensity.adaptivePlatformDensity,
      ),
      home: MyHomePage(title: 'Flutter Demo Home Page'),
    );
  }
}

main.dart に、WelcomeScreen のコードを追記していきます。
Visual Studio Codeなら、 st 入力すると、ショートカットで Flutter Stateless Widgetを入力出来ます。

ここに、assets に追加した背景画像を表示させます。

class WelcomeScreen extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      body: Container(
        width: double.infinity,
        decoration: BoxDecoration(
          image: DecorationImage(
            image: AssetImage("assets/images/Bitmap.png"),
            fit: BoxFit.fill,
          ),
        ),
      ),
    );
  }
}

背景画像が表示されたら、アプリタイトルを追加します。 先程の、Container の child に以下を追加します。

child: Column(
  mainAxisAlignment: MainAxisAlignment.center,
  children: <Widget>[
    RichText(
      text: TextSpan(
        style: Theme.of(context).textTheme.display3,
        children: [
          TextSpan(
            text: "flamin",
          ),
          TextSpan(
              text: ".go",
              style: TextStyle(fontWeight: FontWeight.bold))
        ],
      ),
    ),
  ],
),

フォントカラーを、MyAppのテーマに追記します。

    return MaterialApp(
      debugShowCheckedModeBanner: true,
      title: 'eBook App',
      theme: ThemeData(
        scaffoldBackgroundColor: Colors.white,
        textTheme: Theme.of(context).textTheme.apply( //← この部分を追記
              displayColor: kBlackColor,
            ),

こんな感じになりました。

f:id:momoizo:20200523013843p:plain:h300

スタートボタンを付ける

タイトルの少し下に、スタートボタンをつけます。 ボタンのオブジェクトではなく、Containerを装飾してボタンの見た目にしています。

Container(
  margin: EdgeInsets.symmetric(vertical: 16),
  padding: EdgeInsets.symmetric(vertical: 16, horizontal: 30),
  decoration: BoxDecoration(  // ← 箱型に
    color: Colors.white,
    borderRadius: BorderRadius.circular(30),     // ← でも角丸に
    boxShadow: [ // ← 影をつける
      BoxShadow(
        offset: Offset(0, 15),
        blurRadius: 30,
        color: Color(0xFF666666).withOpacity(.11),
      ),
    ],
  ),
  child: Text(
    "start reading",
    style: TextStyle(
      fontSize: 16,
      fontWeight: FontWeight.bold,
    ),
  ),
),

次にボタンのアクションを付けていきます。 先程のContainerを、切り出してRoundedButton クラスを作成します。

class RoundedButton extends StatelessWidget {
  final String text;
  final Function press;
  final double verticalPadding;
  final double fontSize;

  const RoundedButton({
    Key key,
    this.text,
    this.press,
    this.verticalPadding = 16,
    this.fontSize = 16,
  }) : super(key: key);

  @override
  Widget build(BuildContext context) {
    return GestureDetector(
      onTap: press,
      child: Container( // ← 上記で追加したContainer
        … 
      ),
    );
  }
}

先程、Containerを書いていた箇所を、RoundButtonに置き換えます。

children: <Widget>[
  RichText(
    text: TextSpan(
      style: Theme.of(context).textTheme.display3,
      children: [
        TextSpan(
          text: "flamin",
        ),
        TextSpan(
            text: ".go",
            style: TextStyle(fontWeight: FontWeight.bold))
      ],
    ),
  ),
  RoundedButton(   // ← ここを置き換え
    text: "start reading.",
    fontSize: 20,
  )
],

ボタンが表示されました。

f:id:momoizo:20200523030818p:plain:h300

ボタンのサイズを調整します。 上記の、RoundedButtonを、SizedBoxで囲みます。

既存のWidgetを、新しいWidgetで囲みたくなった場合、該当行の電球をクリックし
「Wrap with widget...」を選択すると、簡単に新しいWidgetで囲むことが出来ます。

f:id:momoizo:20200523031406p:plain

SizedBox(
  width: MediaQuery.of(context).size.width * .6,
  child: RoundedButton(
    text: "start reading.",
    fontSize: 20,
  ),
),

テキストが左寄せになるため、Containerに alignment を設定します。

  @override
  Widget build(BuildContext context) {
    return GestureDetector(
      onTap: press,
      child: Container(
        alignment: Alignment.center,  ← alignment を追加

最後に、RoundButton クラスのソースコードを切り出します。
lib/round_button.dart というファイルを作成し、ソースコードを移動しましょう。

続きます。

VS CodeでDartのSyntax Highlightが効いてない?ときの対処方法

VS CodeでFlutter開発していて、ブログやYoutubeを見ていて気づきました。

あれ?僕のソースコード、色が寂しい…

FlutterのExtension入れたのに、Syntax Highlighting されてなくない?

↓ネット上のカラフルなソース

f:id:momoizo:20200523004012p:plain
よく見るカラフルなソースコード

↓僕の寂しいソース

f:id:momoizo:20200523003746p:plain
寂しいソースコード

あれ?あれ?
なんでやー!

調べたら、VS Codeの Themeの問題でした… Dart syntax highlighting is not highlighting dart code - Stack Overflow

対処方法は、VS Codeのメニューからテーマを選択し…

f:id:momoizo:20200523004216p:plain
Themeメニュー

Dark、、、ではなく、Dark+ を選びます!

f:id:momoizo:20200523004320p:plain
Color選択

ダダーン!カラフルになりました!

f:id:momoizo:20200523004417p:plain
カラフルソースコード

Flutter動画やってみよ(Foodアプリ)5/5

YoutubeのFlutter UI作成動画トライの続きです。

前回はこちら。

食べ物の横スクロール一覧を足していきます。

小見出しを付ける

カテゴリの下に、小見出しをつけます。

      SizedBox(height: 10, ),
    ],
  ),
),
FadeAnimation(1, Padding(
  padding: EdgeInsets.all(20),
  child: Text('Free Delivery', style: TextStyle(color: Colors.grey[700], fontSize: 20, fontWeight: FontWeight.bold))
))

この動画では、Widget間の余白を作るのに、Paddingの他にもSizedBoxが頻繁に使われていますね。
どういう使い分けなんだろう。

f:id:momoizo:20200516143602p:plain
小見出し追加

小見出しが追加されましたが、センタリングされています。 左寄せに変更します。

body: SafeArea(
  child: Column(
    crossAxisAlignment: CrossAxisAlignment.start,

f:id:momoizo:20200516145336p:plain

食べ物画像を表示する

画像の表示エリアを追加します。

FadeAnimation(1, Padding(
  padding: EdgeInsets.all(20),
  child: Text('Free Delivery', style: TextStyle(color: Colors.grey[700], fontSize: 20, fontWeight: FontWeight.bold))
)),
Expanded(
  child: Padding(
    padding: EdgeInsets.symmetric(horizontal: 20.0),
    child: ListView(
      scrollDirection: Axis.horizontal,
      children: <Widget>[
        FadeAnimation(2, makeItem(image: 'assets/images/one.jpg')), 
      ],
    )
  )
),
SizedBox(
  height: 30,
)

今後、複数のアイテムを並べることを想定して、画像表示エリアを生成する makeItem 関数を用意します。
関数は以下です。

  Widget makeItem({image}) {
    return AspectRatio(
      aspectRatio: 1 / 1.4,
      child: GestureDetector(
        child: Container(
          color: Colors.red,
        )
      )
    );
  }

f:id:momoizo:20200516150820p:plain

赤いエリアが表示されました。 ここに画像をはめ込んでいきます。

赤いエリアは、左右の画面の境界線にスワイプすると自然に戻ってきます。 (動きは、Youtubeの 22:00 頃を見てね)

これは、GestureDetector Widgetのおかげかな?

makeItem 関数を書き換えて、画像をはめ込みます。

  Widget makeItem({image}) {
    return AspectRatio(
      aspectRatio: 1 / 1.4,
      child: GestureDetector(
        child: Container(
          decoration: BoxDecoration (
            image: DecorationImage(
              image: AssetImage(image),
              fit: BoxFit.cover,
            )

引数に指定される画像は、アセットのパスなので AssetImage で読み込んでいます。

f:id:momoizo:20200516154235p:plain

美味しそうなピザの画像が出ました!
次に画像に、価格などの文字を重ねるため、黒いグラデーションをかけます。

画像に文字を重ねる

画像に文字を重ねる箇所に、うっすら黒いグラデーションをかけます。

child: Container(
  decoration: BoxDecoration (
    image: DecorationImage(
      image: AssetImage(image),
      fit: BoxFit.cover,
    )
  ),
  child: Container(
    decoration: BoxDecoration(
      gradient: LinearGradient(
        begin: Alignment.bottomCenter,
        stops: [.2, .9],
        colors:[
          Colors.black.withOpacity(.9),
          Colors.black.withOpacity(.3),
        ]
      )
    ),
    child: Padding(
      padding: EdgeInsets.all(20.0),
      child: Column(
        children: <Widget>[
          Align(
            alignment: Alignment.topRight,
            child: Icon(Icons.favorite, color: Colors.white, ),
          )
        ],
      )
    )
  )

画像を配置したコンテナに、グラデーション用のコンテナを追加します。 ついでに、お気に入りアイコンも右上に追加します。

f:id:momoizo:20200516161436p:plain

動画では画面サイズピッタリなのに 僕のは、はみ出してしまってる・・・なぜだ・・・

基本の表示が出来たので、画像を3枚に増やします。

child: ListView(
  scrollDirection: Axis.horizontal,
  children: <Widget>[
    FadeAnimation(1.4, makeItem(image: 'assets/images/one.jpg')), 
    FadeAnimation(1.5, makeItem(image: 'assets/images/two.jpg')), 
    FadeAnimation(1.6, makeItem(image: 'assets/images/three.jpg')), 
  ],

f:id:momoizo:20200517111157p:plain

画像の間隔が詰まりすぎているので、余白を付けます。

  Widget makeItem({image}) {
    return AspectRatio(
      aspectRatio: 1 / 1.5, // ← 1.4 を 1.5 に。
      child: GestureDetector(
        child: Container(
          margin: EdgeInsets.only(right: 20), // margin 設定を追加

Container の右側に margin をつけました。 合わせて、aspectRatio の設定も変更しています。 これで、1画面に収まるようになりました。

f:id:momoizo:20200517112654p:plain

最後に、価格と商品名を追加します。

child: Column(
  crossAxisAlignment: CrossAxisAlignment.start, // ← 左寄せ
  mainAxisAlignment: MainAxisAlignment.spaceBetween, // ← 文字を下側に
  children: <Widget>[
    Align(
      alignment: Alignment.topRight,
      child: Icon(Icons.favorite, color: Colors.white, ),
    ),
    Column(
      crossAxisAlignment: CrossAxisAlignment.start, // ← 左寄せ
      children: <Widget>[ // ←テキスト追加
        Text("\$ 15.00", style: TextStyle(color: Colors.white, fontSize: 40, fontWeight: FontWeight.bold)),
        SizedBox(height: 10,), ← 行間は、SizedBoxで調整
        Text("Vegetarian Pizza", style: TextStyle(color: Colors.white, fontSize: 20)),
      ],
    )

f:id:momoizo:20200517115354p:plain

それっぽい感じに出来ました!

最後に、起動ページをStartupに戻して動作確認すれば終了です。

void main() => runApp(
  MaterialApp(
    debugShowCheckedModeBanner: false,
    theme: ThemeData(fontFamily: 'Roboto'),
    home: StarterPage(), // ← もとのStarterPage から始まるように戻します。
  )
);

Flutter動画やってみよ(Foodアプリ)4/5

YoutubeのFlutter UI作成動画トライの続きです。

前回はこちら。

トップ画面が出来たので、遷移先の商品選択ページを作って行きます。

HomePage.dart を編集していきます。 まずは、Scaffoldの背景色などを整えましょう。

class _HomePageState extends State<HomePage> {
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      backgroundColor: Colors.grey[100],
      appBar: AppBar(
        backgroundColor: Colors.grey[100],
        elevation: 0,
        brightness: Brightness.light,
        leading: Icon(null),
        actions: <Widget>[
          IconButton(
            onPressed: () {},
            icon: Icon(Icons.shopping_basket, color: Colors.grey[800], ),
          )
        ],

まずは、AppBarから。 背景色を変えて、ショピングカートのアイコンを配置しました。

f:id:momoizo:20200516031726p:plain

Bodyに見出しをつけます。

      body: SafeArea(
        child: Column(
          children: <Widget>[
            Padding(
              padding: EdgeInsets.symmetric(horizontal: 20.0),
              child: Column(
                children: <Widget>[
                  FadeAnimation(1, Text('Food Delivery', style: TextStyle(color: Colors.grey[80], fontWeight: FontWeight.bold, fontSize: 30),)),
                ],
              ),
            )
          ],
        ),
      )

animations の下に作成した、FadeAnimation を利用して 見出しにフェードインのアニメーションをつけます。

次に、カテゴリのタグを表示します。

見出しの下につけていきます。

children: <Widget>[
  FadeAnimation(1, Text('Food Delivery', style: TextStyle(color: Colors.grey[80], fontWeight: FontWeight.bold, fontSize: 30),)),
  SizedBox(height: 20,),
  Container(
    height: 50,
    child: ListView(
      scrollDirection: Axis.horizontal,
      children: <Widget>[
        FadeAnimation(1, makeCategory(isActive: true, title: 'Pizza')),
      ],
    )
  )
],

makeCategory関数を作って、カテゴリを表示するWidgetを用意します。

  Widget makeCategory({isActive, title}) {
    return AspectRatio (
      aspectRatio: isActive ? 3 : 2.0 / 1,
      child: Container(
        margin: EdgeInsets.only(right: 10),
        decoration: BoxDecoration(
          color: isActive ? Colors.yellow[700] : Colors.white,
          borderRadius: BorderRadius.circular(50),
        ),
        child: Align(
          child: Text(title, style: TextStyle(color: isActive ? Colors.white : Colors.grey[500], fontSize: 18, fontWeight: isActive ? FontWeight.bold : FontWeight.w100)),
        )
      ),
    );
  }

カテゴリが一つ表示されました。

f:id:momoizo:20200516040634p:plain
カテゴリ表示

カテゴリをもう少し増やしてみます。

Column に CrossAxisAlignment.start を指定して、左寄せにします。

child: Column(
  crossAxisAlignment: CrossAxisAlignment.start,
  children: <Widget>[
    FadeAnimation(1, Text('Food Delivery', style: TextStyle(color: Colors.grey[80], fontWeight: FontWeight.bold, fontSize: 30),)),

カテゴリを固定で増やします。

child: ListView(
  scrollDirection: Axis.horizontal,
  children: <Widget>[
    FadeAnimation(1,   makeCategory(isActive: true, title: 'Pizza')),
    FadeAnimation(1.3, makeCategory(isActive: false, title: 'Burgers')),
    FadeAnimation(1.4, makeCategory(isActive: false, title: 'Kebab')),
    FadeAnimation(1.5, makeCategory(isActive: false, title: 'Desert')),
    FadeAnimation(1.6, makeCategory(isActive: false, title: 'Salad')),
  ],
)

f:id:momoizo:20200516041605p:plain

カテゴリが増えました!

最後に、カテゴリの表示を調整します。

f:id:momoizo:20200516133706p:plain
変更前のカテゴリ

  Widget makeCategory({isActive, title}) {
    return AspectRatio (
      aspectRatio: isActive ? 3 : 2.5 / 1,

Activeではない場合の aspectRatio を 2.0 / 1 → 2.5 / 1 に変更しました。

f:id:momoizo:20200516133853p:plain
変更後のカテゴリ

すこし広めのゆったりサイズになりました。

つづきます。

Flutter モバイルアプリ開発バイブル

Flutter モバイルアプリ開発バイブル