Tech Racho エンジニアの「?」を「!」に。
  • 開発

Flutter: 標準ウィジェットをコピーして改造する

追記(2020/07/20)

記事投稿から1年7ヶ月、本記事で題材にしたスクロールバーの太さの変更についてカスタマイズ可能になるコミットが3日前にmasterへマージされました。

https://github.com/flutter/flutter/commit/1840b7121a6a721484afca300265c7866e82dc77



flutter.ioより

突然ですがFlutterは面白いですよ!
クロスプラットフォーム開発ファンとして最近の一押しです。

そこでFlutterでちょっとアドベントカレンダー記事を書いてみます。


はじめに

モバイルアプリ開発においては、画面上にはボタンやツールバーなど様々なUIウィジェットを配置します。
iOS/AndroidのSDKも標準で豊富なUIウィジェットを持っていますが、それらを「少しだけカスタマイズしたい」という事がよくあります。このような...

代理店で仕事をしていて、作成したすべてのアプリを同じように見せることはできないものです。私は彼らに独特の表情を見せて、スタイリッシュなタッチを加える必要があります。

引用させていただいた記事では

サブクラスを作り、それを自分でデザインします。

という方法が紹介されていますが、そんなに大きく変更したいわけではないが元のウィジェットがサブクラスで一部分だけ変更することが出来ず、かといって全てを自作したり上書きするもの大変すぎる、という事があります。

幸いにしてFlutterは全てのウィジェットのソースコードが公開されているので、それを「コピーして改造する」という方法が使えます。
Android開発においても同様の事はたまに行われるようですが、普段iOS開発をしている身からするとなんと素晴らしく...うらやましい...UI○○のここ変えられないんかよ...

...さて、それでは実際に標準ウィジェット、今回はScrollbarウィジェットをコピーして改造してみようと思います。
なお、検証に使用したFlutter SDKのバージョンは以下(執筆時のmaster)となります。

Flutter 0.11.14-pre.1 • channel master • https://github.com/flutter/flutter.git
Engine • revision 7375a0f414
Tools • Dart 2.1.0 (build 2.1.0-dev.9.4 f9ebf21297)

やってみる

題材として、まず100件のリスト表示を行うアプリmyappを作ります。

import 'package:flutter/material.dart';

void main() => runApp(MyApp());

class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      home: Scaffold(
        appBar: AppBar(),
        body: Scrollbar(
          child: ListView.builder(
            itemCount: 100,
            itemBuilder: (BuildContext context, int index) {
              return ListTile(title: Text('$index'));
            },
          ),
        ),
      ),
    );
  }
}

見事完成しました!🎊🎉

...が、ここで「スタイリッシュなタッチを加える」べく、スクロールバーの太さを3倍にしなければならなくなった、とします 😰

まずはScrollbarウィジェットに太さを設定する方法がないか調べますが、どうも見つかりません。

そこで

サブクラスを作り、それを自分でデザインします。

を実践してみようとしますが、こちら試してみるとわかるのですがScrollbarウィジェットはサブクラスでデザインを変更するのに向かない作りをしています(詳細は割愛)。

こうなると全てを自分で作るか、細かな設定が可能なライブラリが無いかpub を探し回り良さそうなものにスクロールバーを置き換えるか...というところですが、なんとかScrollbarウィジェットのままカスタマイズする方法はないものか。

ここで、先程サブクラスでのデザイン変更を試すべくScrollbarウィジェットの実装ファイル scrollbar.dart を確認している時に、ものすごくそれらしい定数 _kScrollbarThickness を見かけていることを思い出します。

この値はサブクラスでオーバーライドできるような性質のものではありませんが、この値を書き換える事が出来れば...?

ということで、Scrollbarウィジェットを直接改造すべく scrollbar.dart Flutter SDKのディレクトリ内からコピーしてきて、プロジェクトの中に取り込んでみます。が、

エラーがあるようです。何が起きているのか見てみると

どうやらFlutter SDKの中にいた時は theme.dart から import出来ていた Theme クラスがimport出来ていないようです。
また、どうもこの theme.dart はアプリのプロジェクトからは直接読み込めない様子。

そこで、 Theme クラスのリファレンスを確認すると

Flutter > material > Theme と、Themematerial に含まれていそうなので material を確認してみます。

import package:flutter/material.dart のようなので、これに書き換えてみます。

うまくいきました ☺

また、実は material.dart をimportすると widgets.dart もimportされた事になるため

import 'package:flutter/widgets.dart'; は削除することができます。

これで手元にエラーのない scrollbar.dart のコピーが取れたので、はりきって _kScrollbarThickness の値を3倍の18.0に書き換え、あとは読み込んで使うだけです...

おっとエラーです、SDK側の Scrollbar と重複してしまいました。どうしよう。

案その1として、手元の scrollbar.dart のスクロールバー実装のクラス名を別のものにする手がありますが、Flutterの開発言語Dartではprefix付きのimport、が出来ます。

as custom としてimportすることで、手元の scrollbar.dart から公開されるScrollbarウィジェットは custom.Scrollbar として標準のものと別に扱うことが出来ます。

  • 他にもDartではimportされるものの一部のみを見せたり逆に一部を隠す事も出来るため、今回の例では import 'package:flutter/material.dart' hide Scrollbar; として標準のScrollbarを隠す、という方法もあります。

さて、ついにカスタマイズしたScrollbarウィジェットを使うことが出来たようです...?

太さが3倍になりました 👍

おわりに

以上、標準ウィジェットをコピーして改造する一連の流れを見ていただきました。
なお、ウィジェットによってはこのようにうまくいかないものもあるかと思います。変えたいと思った場所が複雑な作りになっていたりそもそも変えられるように出来ていなかった、手元にコピーするには関連するウィジェットも合わせてコピーしてくる必要がある、など...。
さらには「その時点の実装をコピーして来る」ということは、今後SDKのバージョンアップに伴って修正されたバグや追加された機能はもちろん手元のコピーには反映されません(これは一長一短ではありますが)。
ある日コピー+改造で実現したことは、いつかカスタマイズ可能な設定になっているかもしれません。

ですが、「その気になればこうして対応する術がある」というのは心強いものです。
頭の片隅に留めておいていただくと、困った時の助けになるかもしれません ☺

追記(2020/07/20)

冒頭の追記に加え、

追加された機能はもちろん手元のコピーには反映されません

例えばしばらく(4ヶ月)前にFlutterのデスクトップOS向けの使用を考慮し、スクロールバーを自動で消さないようにする isAlwaysShown という機能が増えたりもしました。

https://github.com/flutter/flutter/commit/ac3b77bdaca677bbea58be11d22317879d363813


CONTACT

TechRachoでは、パートナーシップをご検討いただける方からの
ご連絡をお待ちしております。ぜひお気軽にご意見・ご相談ください。