Skip to content

Latest commit

 

History

History
492 lines (362 loc) · 22.4 KB

README.jp.md

File metadata and controls

492 lines (362 loc) · 22.4 KB

Pub Pub Popularity Docs

exprollable_page_view 🐦

スクロールに合わせてページが拡大(縮小)するPageViewです。 Exprollableはexpandableとscrollableを組み合わせた造語です。このパッケージはiOS版Apple BooksアプリのセミモーダルUIを再現しようという試みです。

このWidgetを使えばこのようなUIを簡単に作成することができます:

アナウンス

XX-XX-2023

安定版のバージョン1.0.0がリリースされました。詳しくは マイグレーションガイドをご覧ください。

2023/05/17

バージョン1.0.0-rc.1がリリースされました 🎉!このバージョンはいくつかの破壊的な変更を含んでいます。もしベータ版(1.0.0-beta.xx)をすでに使用中の場合、コードの修正が必要になるかもしれません。詳しくは マイグレーションガイドをご覧ください。

目次

試してみる

サンプルアプリでこのパッケージが提供する全ての機能を試すことができます。

git clone git@github.com:fujidaiti/exprollable_page_view.git
cd example
flutter pub get
flutter run

またExprollablePageViewとGoogle Maps APIを統合するサンプルもあります。詳しくは maps_example/READMEをご覧ください。

インストール

pub.dev上で公開されているので、pubコマンドからインストールできます。

flutter pub add exprollable_page_view

使い方

特定の使用方法をお探しの場合はHow-toセクションをご覧下さい。

ExprollablePageView

ExprollablePageViewは通常のPageViewと同じように使うことができます。各ページにはScrollControllerを取り付けることができるスクロール可能なWidget(例えばListView)を配置してください。ただし、必ずPageContentScrollController.of経由で取得したScrollControllerを使用してください

import 'package:exprollable_page_view/exprollable_page_view.dart';

@override
Widget build(BuildContext context) {
  return Scaffold(
    body: ExprollablePageView(
      itemCount: 5,
      itemBuilder: (context, page) {
        return ListView.builder(
          // これを忘れずに
          controller: PageContentScrollController.of(context),
          itemBuilder: (context, index) {
            return ListTile(title: Text('Item#$index'));
          },
        );
      },
    ),
  );
}

ExprollablePageViewのコンストラクタはPageView.builderコンストラクタとほとんど同じシグネチャを持ちます。各パラメータの詳細はPageViewの公式ドキュメントをご覧ください。

  const ExprollablePageView({
    Key? key,
    IndexedWidgetBuilder itemBuilder,
    int? itemCount,
    ExprollablePageController? controller,
    bool reverse = false,
    ScrollPhysics? physics,
    DragStartBehavior dragStartBehavior = DragStartBehavior.start,
    bool allowImplicitScrolling = false,
    String? restorationId,
    Clip clipBehavior = Clip.hardEdge,
    ScrollBehavior? scrollBehavior,
    bool padEnds = true,
    void Function(PageViewportMetrics metrics)? onViewportChanged,
    void Function(int page)? onPageChanged,
  });

ExprollablePageController

PageViewPageControllerと同様に、ExprollablePageControllerExprollablePageViewの表示ページを制御するオブジェクトです。またviewportの振る舞いを制御するためにも使用されます。

final controller = ExprollablePageController(
  initialPage: 0,
  viewportConfiguration: ViewportConfiguration(
    minFraction: 0.9,
  ),
);

ViewportConfigurationのパラメータを調整することでviewportの振る舞いを細かく決めることができます。

factory ViewportConfiguration({
  bool overshootEffect = false,
  double minFraction = 0.9,
  double maxFraction = 1.0,
  ViewportInset shrunkInset = ViewportInset.shrunk,
  ViewportInset? initialInset,
  List<ViewportInset> extraSnapInsets = const [],
});
  • minFraction:viewportに対する各ページの最小縮小率
  • initialInsetviewport insetの初期値
  • shrunkInset: ページが完全に縮小されるときのviewport inset
  • extraSnapInsets: ページをスナップさせるinsetのリスト(詳しくは[ページビューをボトムシートのようにする](#make-the-page-view like-a-bottomsheet) を参照)
  • overshootEffect:overshoot effectが有効かどうか(詳しくはオーバーシュート効果セクションを参照)

Viewport fraction と inset

viewportの状態はfractioninsetの2つによって表されます。fractionは各ページのサイズがviewportに対してどのくらいの割合であるかを示し、またinsetはviewportの上部から現在のページの上部までの距離を表します。これらの状態はViewportクラスが管理しており、ExprollablePageControllerを通じて参照することができます。詳しくはviewportの状態を監視するをご覧ください。

viewport-fraction-and-inset

ViewportInsetはinset値を表現するためのクラスです。事前定義されたinsetは3種類あり、それぞれ定数として定義されています。

  • ViewportInset.expanded: ページが完全に拡大されるデフォルトのinset
  • ViewportInset.shrunk : ページが完全に縮小されるデフォルトのinset
  • ViewportInset.overshoot : overshoot effectを有効にする際に使用されるinset

ユーザー定義のinsetはViewportInset.fixedViewportInset.fractionalから作成できます。またViewportInsetを継承したクラスを作成することでより複雑な計算を実行することができます。これらのクラスの使用例はmake the PageView like a BottomSheet, observe the state of the viewportで見ることができます。

viewport-insets

Overshoot effect

overshoot effectはページが拡大される際の視覚効果です。これが有効になっている場合、ページがフルスクリーンになる時、ページの上部がviewportの上部を少しだけはみ出すような挙動をとります。より正確に言えば、fractionが1になる時insetが負の値をとるということです。これにより図のようなダイナミックな視覚効果が得られます(左:overshoot effectあり、真ん中:なし)。Apple BooksアプリのUIでもこのような挙動が実現されています(右図)。

ViewportConfiguration

ViewportConfiguration はviewportの動作をカスタマイズするための柔軟な方法を提供します。ほとんどのケースではViewportConfigurationの無名コンストラクタで十分だと思いますが、より細かい制御が必要な場合は、ViewportConfiguration.rawを使用してください。viewport fractionとinsetの範囲、またページが完全に拡大(縮小)する位置を指定することができます。以下のコードはページを4つの状態にスナップさせるExprollablePageControllerの例です。

  • Collapsed: ページがほとんど隠れた状態
  • Shrunk: bottom sheetのような状態
  • Expanded: Shrunkと似ているが、ページが拡大されている状態
  • Fullscreen: 全画面状態
const fullscreenInset = ViewportInset.fixed(0);
const expandedInset = ViewportInset.fractional(0.2);
const shrunkInset = ViewportInset.fractional(0.5);
const collapsedkInset = ViewportInset.fractional(0.9);
final controller = ExprollablePageController(
  viewportConfiguration: ViewportConfiguration.raw(
    minInset: fullscreenInset,
    expandedInset: expandedInset,
    shrunkInset: shrunkInset,
    maxInset: collapsedInset,
    initialInset: collapsedInset,
    snapInsets: [
      fullscreenInset,
      expandedInset,
      shrunkInset,
      collapsedInset,
    ],
  ),
);

ModalExprollable

ModalExprollableを使用すれば、半透明の背景と「下にスワイプして閉じる」アクションを持つモーダルダイアログ形式のExprollablePageViewを簡単に作成することができます。showModalExprollable関数は、ExprollablePageViewModalExprollableでラップし、ダイアログとして表示する便利な方法です。ダイアログの表示/非表示時の動作をカスタマイズする場合は、独自のPageRouteを作成しModalExprollableその中でModalExprollableを使用してください。

showModalExprollable(
    context,
    builder: (context) {
      return ExprollablePageView(...);
    },
  );

より詳しい例は modal_dialog_example.dart をご覧ください。

スライドアクションを持つリストアイテム

組み込みのPageViewに対するExprollablePageViewの利点の一つは、flutter_slidableなどの水平方向のスライドアクションを持つウィジットをページ内で使用できることです。より詳しい例はexample/lib/src/complex_example/album_details.dartで見ることができます。

How-to

現在のページを取得する

ExprollablePageController.currentPageから取得できます。

final page = pageController.currentPage.value;

currentPageValueListenable<int> 型なので、リスナーを作成して変更を検知することもできます。

pageController.currentPage.addListener(() {
  final page = pageController.currentPage.value;
});

別の方法として、ExprollablePageView.onPageChangedコールバックを指定することでページの変更を監視することができます。これは内部的に上記のコードと等価なことをしています。

ExprollablePageView(
  onPageChanged: (page) { ... },
);

PageViewをBottomSheetのように表示する

ExprollablePageControllerViewportConfigurationを使用してください。以下は、3つの状態にスナップするためのコントローラの例である:

  1. ページが完全に展開されている (Viewport.fraction == 1.0)
  2. ページがビューポートより少し小さい (Viewport.fraction == 0.9)
    1. Viewport.fraction == 0.9で、ページビューがボトムシートのように画面の半分だけをカバーする。

コードの詳細は、custom_snap_offsets_example.dartを参照してください。

controller = ExprollablePageController(
  viewportConfiguration: ViewportConfiguration(
    minFraction: 0.9,
    extraSnapInsets: [
      ViewportInset.fractional(0.5),
    ],
  ),
);

viewportの状態を監視する

viewportの状態を監視する方法は3つあります。

1. ExprollablePageController.viewportを監視する

ExprollablePageController.viewportValueListenable<ViewportMetrics> で、 ViewportMetrics にはビューポートの現在の状態が含まれます。したがって、ビューポートの状態に依存するアクションを実行するために、ビューポートをリッスンして使用することができます。

controller.viewport.addListener(() {
  final ViewportMetrics vp = controller.viewport.value;
  final bool isShrunk = vp.isPageShrunk;
  final bool isExpanded = vp.isPageExpanded;
});

2. ViewportUpdateNotificationを監視する

ExprollablePageViewは、状態が変化するたびに ViewportUpdateNotification をディスパッチし、その中に ViewportMetrics が含まれます。この通知は NotificationListener ウィジェットを使って聞くことができます。ウィジェットツリーで NotificationListenerExprollablePageView の祖先であることを確認してください。このメソッドは、状態依存のアクションを実行したいが、コントローラがない場合に便利です。

NotificationListener<ViewportUpdateNotification>(
        onNotification: (notification) {
          final ViewportMetrics vp = notification.metrics;
          return false;
        },
        child: ...,

3. onViewportChanged コールバックを使用する

ExprollablePageViewのコンストラクタは、ビューポートの状態が変化するたびに呼び出されるコールバックを受け取ります。

ExprollablePageView(
  onPageViewChanged: (ViewportMetrics vp) {...},
);

ページ間にスペースを追加する

各ページをPageGutterで囲ってください。

ExprollablePageView(
  itemBuilder: (context, page) {
    return PageGutter(
      gutterWidth: 12,
      child: ListView(...),
    );
  },
);

overshoot effectが有効なときapp barが画面の外に飛び出してしまうのを防ぐ

AdaptivePagePaddingを使用します。このウィジェットは、現在のビューポートのオフセットに従って、子ウィジェットに適切なパディングを追加します。コードの例は adaptive_padding_example.dart にあります。

Container(
  color: Colors.lightBlue,
  child: AdaptivePagePadding(
    child: SizedBox(
      height: height,
      child: const Placeholder(),
    ),
  ),
);

viewportの状態をアニメーションで動かす

ExprollablePageController.animateViewportInsetToを使用します。以下のコードは1秒かけてスクロールアニメーションと共にページを縮小する例です。

// Shrunk the current page with scroll animation in 1 second.
controller.animateViewportInsetTo(
  ViewportInset.shrunk,
  curve: Curves.easeInOutSine,
  duration: Duration(seconds: 1),
);

より具体的な例はanimation_example.dartで見ることができます。

マイグレーションガイド

1.0.0-rc.x から 1.x

バージョン1.0.0以前では、overshoot effectは以下の条件を満たした場合のみ正しく機能しました。

  • MediaQuery.padding.bottom > 0
  • NavigationBarBottomAppBarのようなwidgetがExprollablePageViewの下部を覆い隠している

バージョン1.0.0からは上記の制限がなくなり、Scaffold.bottomNavigationBarScaffold.extendBodyの有無に関わらずovershoot effectを有効にすることができるようになりました。

  controller = ExprollablePageController(
    viewportConfiguration: ViewportConfiguration(
     overshootEffect: true,
    ),
  );
  
  Widget build(BuildContext context) {
    return Scaffold(
      // 以下の2行はバージョン1.0.0以降では不要になりました
      // extendBody: true,
      // bottomNavigationBar: BottomNavigationBar(...),
      body: ExprollablePageView(
        controller: controller,
        itemBuilder: (context, page) { ... },
      ),
    );
  }

^1.0.0-beta から ^1.0.0-rc.1

1.0.0-rc.1のリリースに伴い、いくつかの破壊的変更があります。

PageViewportMetricsの変更

PageViewportMetricsmixinは ViewportMetrics mixinに統合され、削除されました。またそれに伴い一部のプロパティ名も変更されています。以下に従ってコード内のシンボル名を置き換えてください:

  • PageViewportMetrics 👉 ViewportMetrics
  • PageViewportMetrics.isShrunk 👉 ViewportMetrics.isPageShrunk
  • PageViewportMetrics.isExpanded 👉 ViewportMetrics.isPageExpanded
  • PageViewportMetrics.xxxOffset 👉 ViewportMetrics.xxxInset (接尾辞OffsetInsetで置換)

またPageViewportMetrics.overshootEffect は削除されました。

ViewportControllerの変更

ViewportController クラスは PageViewport に名前が変更されました。またPageViewportViewportMetrics を実装していません(削除されたため)。

ViewportOffsetの変更

For ViewportOffset and its inherited classes, the suffix Offset was replaced with the new suffix Inset, and 2 new inherited classes were introduced (see Viewport fraction and inset).

  • ViewportOffset 👉 ViewportInset

  • ExpandedViewportOffset 👉 DefaultExpandedViewportinset

  • ShrunkViewportOffset 👉 DefaultShrunkViewportInset

ExprollablePageControllerの変更

ViewportConfigurationの導入に伴い、ExprollablePageControllerのコンストラクタのシグネチャも変更されました。

Before:

  final controller = ExprollablePageController(
    initialPage: 0,
    minViewportFraction: 0.9,
    overshootEffect: true,
    initialViewportOffset: ViewportOffset.shrunk,
  );

After:

final controller = ExprollablePageController(
  initialPage: 0,
  viewportConfiguration: ViewportConfiguration(
    minFraction: 0.9,
    overshootEffect: true,
    initialInset: ViewportInset.shrunk,
  ),
);

加えてExprollablePageController.withAdditionalSnapOffsets も削除されたため、代わりにViewportConfiguration.extraSnapInsetsを使用してください。詳しくはExprollablePageControllerをご覧ください。

Before:

final controller = ExprollablePageController.withAdditionalSnapOffsets([
  ViewportOffset.fractional(0.5),
]);

After:

final controller = ExprollablePageController(
  viewportConfiguration: ViewportConfiguration(
    extraSnapOffset: [ViewportInset.fractional(0.5)],
  ),
);

その他シンボル名の変更

  • StaticPageViewportMetrics 👉 StaticViewportMetrics
  • PageViewportUpdateNotification 👉 ViewportUpdateNotification
  • PageViewport 👉 Viewport