リバース・エンジニアリング

Flultterとテックブログと時々iOS

39.【AppleMusicクローン】クラスの分離を行う

前回のAppleMusicクローンアプリの続きです。

前回はmain.dartにレイアウトをただ並べて行きました。 その結果、一つのクラスでいろいろなクラスがごちゃまぜになりました。

blog.tamappe.com

今回はこのまま次の行の横スクロールを実装していくと メンテナンスが大変になることを想定してレイアウトの分離を行います。

前回はヘッダーとListViewを作成しましたので「ヘッダー」部分と「ListView」部分を分けたいと思います。

  • custom_sliver_appbar.dart (ヘッダー)
  • custom_sliver_list.dart (ListView)

命名でクラスを作成します。

ヘッダー部分のソースコード

custom_sliver_appbar.dart

import 'package:flutter/material.dart';

class CustomAppBar extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return SliverAppBar(
      pinned: false,
      backgroundColor: Colors.white,
      expandedHeight: 40.0,
      flexibleSpace: FlexibleSpaceBar(
        titlePadding: EdgeInsets.only(left: 20),
        centerTitle: false,
        title: Text(
          '見つける',
          style: TextStyle(color: Colors.black),
        ),
      ),
    );
  }
}

ListView部分のソースコード

custom_sliver_list.dart

import 'package:flutter/material.dart';

class ContentSliverList extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return SliverList(
      delegate: SliverChildBuilderDelegate(
            (BuildContext context, int index) {
          if (index == 0) {
            return Padding(
              padding: const EdgeInsets.all(5.0),
              child: Container(
                height: 250,
                child: ListView(
                  // This next line does the trick.
                  scrollDirection: Axis.horizontal,
                  children: <Widget>[
                    Padding(
                      padding: const EdgeInsets.all(5.0),
                      child: Container(
                        width: MediaQuery.of(context).size.width - 40.0,
                        color: Colors.red,
                      ),
                    ),
                    Padding(
                      padding: const EdgeInsets.all(5.0),
                      child: Container(
                        width: MediaQuery.of(context).size.width - 40.0,
                        color: Colors.blue,
                      ),
                    ),
                    Padding(
                      padding: const EdgeInsets.all(5.0),
                      child: Container(
                        width: MediaQuery.of(context).size.width - 40.0,
                        color: Colors.green,
                      ),
                    ),
                    Padding(
                      padding: const EdgeInsets.all(5.0),
                      child: Container(
                        width: MediaQuery.of(context).size.width - 40.0,
                        color: Colors.yellow,
                      ),
                    ),
                    Padding(
                      padding: const EdgeInsets.all(5.0),
                      child: Container(
                        width: MediaQuery.of(context).size.width - 40.0,
                        color: Colors.orange,
                      ),
                    ),
                  ],
                ),
              ),
            );
          } else {
            return Card(
              child: ListTile(
                title: Text("list item:$index"),
                leading: Icon(Icons.photo),
              ),
            );
          }

        },
        childCount: 20,
      ),
    );
  }
}

main.dartの部分

main.dart

import 'package:apple_music_clone/widgets/content_sliver_list.dart';
import 'package:apple_music_clone/widgets/custom_sliver_appbar.dart';
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(
      title: 'Flutter Demo',
      theme: ThemeData(),
      home: HomePage(title: '見つける'),
    );
  }
}

class HomePage extends StatefulWidget {
  HomePage({Key key, this.title}) : super(key: key);

  final String title;

  @override
  _HomePageState createState() => _HomePageState();
}

class _HomePageState extends State<HomePage> {
  bool _isVisibleHeader;
  ScrollController _scrollController;

  @override
  void initState() {
    super.initState();
    _scrollController = ScrollController();
    _scrollController.addListener(_scrollListener);
    _isVisibleHeader = true;
  }

  @override
  void dispose() {
    _scrollController.dispose();
    super.dispose();
  }

  void _scrollListener() {
    print(_scrollController.offset);

    if (_scrollController.offset > 100) {
      setState(() {
        _isVisibleHeader = true;
      });
    } else {
      setState(() {
        _isVisibleHeader = false;
      });
    }
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        elevation: _isVisibleHeader ? 1.0 : 0.0,
        backgroundColor: Colors.white,
        title: Visibility(
            visible: _isVisibleHeader,
            child: Text(
              '見つける',
              style: TextStyle(color: Colors.black),
            )),
      ),
      body: CustomScrollView(
        shrinkWrap: false,
        controller: _scrollController,
        slivers: <Widget>[
          CustomAppBar(),
          ContentSliverList()
        ],
      ), // This trailing comma makes auto-formatting nicer for build methods.
    );
  }
}

ソースコードをビルドするとコンテンツ部分のセクション1が横スクロールできます。

f:id:qed805:20200401002359p:plain
ビルド時の挙動

次回からはcustom_sliver_list.dartにどんどんレイアウトを乗せていく予定になります。 セクションの概念を導入する予定なのでDartenum を使ってセクション分けしていきます。