フラッタのスクロール可能な探索見える


あなたは別の高さで複数のウィジェットのスクロールビューでアプリケーションを構築する必要がありますし、ユーザーが自動的に別のウィジェットからスクロールできるようにリンクを追加したいですか?そのような例の1つは、記事またはメニューの目次であるかもしれません.
このポストでは、私はどのようにこれを実装することができますし、それを行うためにショーケース私は、私はいくつかのセクションとコンテンツのテーブルを画面の上部に対応するセクションへのリンクを使用して画面を使用するつもりです.
のを見ましょう!

This post is based on Flutter 2.5.2 and Dart SDK 2.14.3



解決策
そうするために、我々は使用するつもりです Scrollable.ensureVisible SingleChildScrollView ウィジェットをColumn チャイルド.
簡単に言えば、我々はGlobalKey() セクションごとに.次に、このキーを使用して、リンクがターゲットになるウィジェットのキーを使用します.最後に、次のスニペットのような内容のテーブルからリンクを押すと、このキーを使用します.
final targetContext = targetKey.currentContext;
if (targetContext != null) {
    Scrollable.ensureVisible(
        targetContext,
        duration: const Duration(milliseconds: 400),
        curve: Curves.easeInOut,
    );
}
その結果、ターゲットウィジェットは400ミリ秒後に表示されます.
次のセクションで、私は実装のより詳細なプレゼンテーションをしようとします!

実装

準備作業
まず、セクションを表すデータ構造を作成しましょう.それは、キー、タイトルと体があります.
class Section {
  final GlobalKey key;
  final String title;
  final String body;

  const Section(this.key, this.title, this.body);
}
タイトルは目次に使用され、キーはターゲットセクションにスクロールするために使用されます.
次に、このクラスを使用してダミーデータを生成します.いくつかの長いテキストを表現するためにいくつかのLorem IPsumを使用することができます.
const reallyLongBody =
    'Lorem ipsum dolor sit amet, consectetur adipiscing elit. Fusce venenatis pharetra dui, ac semper nulla dapibus ultrices.'
    ' Pellentesque sed erat accumsan lorem rhoncus mattis eu eget nulla. Phasellus sagittis vehicula dapibus. Nulla dolor nunc, '
    'feugiat ac ullamcorper vel, commodo sed lacus. Nunc volutpat rutrum euismod. Nullam venenatis imperdiet odio, non porta leo '
    'ullamcorper ac. Aliquam fringilla mauris ut ante faucibus, non tempus elit placerat. Donec sed porttitor tellus. Donec lobortis '
    'arcu id lectus commodo varius. Fusce tincidunt ante in faucibus suscipit. Nulla facilisi. Nunc at nibh dictum sem aliquet '
    'consectetur eu nec neque. Nullam ullamcorper vulputate nisl quis pharetra. Etiam dapibus ullamcorper magna, a iaculis libero '
    'dignissim in. Vestibulum dictum, justo posuere consectetur eleifend, augue mi dictum dui, eu sollicitudin elit mauris vel lacus. '
    'Donec dui felis, dapibus vel urna at, commodo facilisis felis.\nCurabitur faucibus leo ipsum, in vehicula risus rhoncus id. Donec '
    'ac velit quis nulla suscipit efficitur. Nulla non euismod neque. Sed blandit urna sed ex tempor sagittis. Curabitur condimentum nec '
    'dui quis sollicitudin. Proin consectetur, metus sed rutrum varius, mi augue placerat est, sed posuere risus nunc ac urna. Nam leo '
    'erat, bibendum non nibh sed, sollicitudin aliquet metus. Aliquam finibus turpis vitae leo laoreet molestie.';

final sections = [
  Section(GlobalKey(), '1. Section', reallyLongBody),
  Section(GlobalKey(), '2. Section', reallyLongBody),
  Section(GlobalKey(), '3. Section', reallyLongBody),
  Section(GlobalKey(), '4. Section', reallyLongBody),
  Section(GlobalKey(), '5. Section', reallyLongBody),
  Section(GlobalKey(), '6. Section', reallyLongBody),
];

ウィジェット
次はウィジェットのウィジェットを作成しましょうSection , ここでは、各セクションのタイトルとボディを表示する予定です
class SectionWidget extends StatelessWidget {
  final Section section;

  const SectionWidget({Key? key, required this.section}) : super(key: key);

  @override
  Widget build(BuildContext context) {
    return Container(
        decoration: const BoxDecoration(
          color: Color(0xfffff8e2),
          borderRadius: BorderRadius.all(Radius.circular(8.0)),
        ),
        margin: const EdgeInsets.symmetric(horizontal: 16, vertical: 8),
        padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 8),
        child: Column(
          crossAxisAlignment: CrossAxisAlignment.stretch,
          children: [
            Text(
              section.title,
              textAlign: TextAlign.center,
              style: Theme.of(context)
                  .textTheme
                  .headline2
                  ?.copyWith(color: Colors.black),
            ),
            const SizedBox(
              height: 36,
            ),
            Text(
              section.body,
              style: Theme.of(context)
                  .textTheme
                  .bodyText1
                  ?.copyWith(color: Colors.black54, height: 1.3),
            )
          ],
        ));
  }
}
その後、セクションへのリンクのウィジェットを作成しましょう.名前を付けますSectionLink そして、我々はセクションとコールバックのためにonTap イベントInkWell .
class SectionLink extends StatelessWidget {
  final Section section;
  final void Function(Section) onTap;

  const SectionLink({Key? key, required this.section, required this.onTap})
      : super(key: key);

  @override
  Widget build(BuildContext context) {
    return InkWell(
      onTap: () => onTap(section),
      child: Padding(
        padding: const EdgeInsets.all(8.0),
        child: Text(
          section.title,
          style: Theme.of(context)
              .textTheme
              .headline3
              ?.copyWith(color: Colors.black87, fontWeight: FontWeight.bold),
        ),
      ),
    );
  }
}
次はAを加える予定ですTableOfContents ウィジェットは、基本的にセクションを繰り返し、各セクションについて、我々はSectionLink .
class TableOfContents extends StatelessWidget {
  final List<Section> sections;
  final void Function(Section) onItemTap;

  const TableOfContents({
    Key? key,
    this.sections = const <Section>[],
    required this.onItemTap,
  }) : super(key: key);

  @override
  Widget build(BuildContext context) {
    return Container(
      margin: const EdgeInsets.all(20),
      padding: const EdgeInsets.only(left: 16, top: 24, right: 16, bottom: 10),
      decoration: BoxDecoration(
        color: Colors.white12.withOpacity(0.3),
        borderRadius: const BorderRadius.all(Radius.circular(8.0)),
        border: Border.all(
          width: 2,
          color: Colors.grey,
        ),
      ),
      child: Column(
        crossAxisAlignment: CrossAxisAlignment.stretch,
        children: sections
            .map((e) => SectionLink(section: e, onTap: onItemTap))
            .toList(),
      ),
    );
  }
}
最後に、作成しましょうArticlePage どこで我々はすべてを結びつけるでしょう.このウィジェットではSingleChildScrollViewColumn を含むウィジェットTableOfContentsListView セクションで.
のためにTableOfContents , セクションをタップするとセクションとコールバックのパラメータとして渡されます.このコールバックは、ターゲットウィジェットにスクロールするロジックを含んでいる.まず、このキーを持つウィジェットがツリーにあることを確認しますcurrentContext がNULLでない.そして、この文脈をScrollable.ensureVisible ターゲットウィジェットにスクロールする.
のためにListView , 我々はセクションの上で繰り返します、そして、各々のために、我々は新しいものをつくりますSectionWidget ウィジェットのキーとしてセクションからキーを使用する.
class ArticlePage extends StatelessWidget {
  final List<Section> sections;
  const ArticlePage({Key? key, required this.sections}) : super(key: key);

  @override
  Widget build(BuildContext context) {

    final tableOfContents = TableOfContents(
      sections: sections,
      onItemTap: (section) {
        final targetContext = section.key.currentContext;
        if (targetContext != null) {
          Scrollable.ensureVisible(
            targetContext,
            duration: const Duration(milliseconds: 400),
            curve: Curves.easeInOut,
          );
        }
      },
    );

    final listView = ListView.builder(
      shrinkWrap: true,
      physics: const NeverScrollableScrollPhysics(),
      itemCount: sections.length,
      itemBuilder: (BuildContext context, int index) {
        final section = sections[index];

        return SectionWidget(
          key: section.key,
          section: section,
        );
      },
    );

    return Scaffold(
      appBar: AppBar(
        title: const Text('Home Screen'),
      ),
      body: SafeArea(
        child: SingleChildScrollView(
          child: Padding(
            padding: const EdgeInsets.all(8.0),
            child: Column(
              crossAxisAlignment: CrossAxisAlignment.stretch,
              children: [
                tableOfContents,
                listView,
              ],
            ),
          ),
        ),
      ),
    );
  }
}
アプリケーションを構築して実行する場合は、コンテンツの表からのリンクを押すと、次のビデオの動作と同じになります.

You can also use dartpad.dev to find and run the code of this post.



より多くのオプション
今、我々は我々が利用できる方法を見ましたScrollable.ensureVisible , ターゲットウィジェットへの移行をカスタマイズするためにいくつかのオプションを調べましょう.
前の例で前に見たもののうちの2つはduration and curve . duration ターゲットのウィジェットへのリンクからアニメーションをしたい目的の期間を設定するために使用することができます.
curve パラメータは、移行が続くアニメーションカーブを定義できます.基本的に、一定の速度でトランジションを行う代わりに、curve パラメータを時間をかけてアニメーションを変更するには、いずれかをスピードアップまたは特定の時間枠でそれを減速します.このような値を取ることができますCurves.bounceInOut , Curves.easeInOut , 例えば、Curves.easeInOut アニメーションは徐々に起動し、スピードアップし、ゆっくりと終了します.

Note: A visualization of the different options can be found on api.flutter.dev.


もう一つのパラメータScrollable.ensureVisible is alignment , そして、それはターゲットウィジェットの位置を設定するために使用することができます.値が0.0である場合、子はビューポートの前縁に近い位置に配置されます.
最後にalignmentPolicy を使用すると、ポリシーを決定するときにalignment パラメータこのパラメータはScrollPositionAlignmentPolicy , これは以下のオプションを持つenumです:explicit , keepVisibleAtEnd or keepVisibleAtStart .
When it is set to explicit , これはalignment ターゲットオブジェクトを配置する場所を決定するプロパティ.If it is set to keepVisibleAtEnd , ターゲットアイテムの一番下の端がスクロールコンテナーの一番下の端の下にある場合、ターゲット項目の一番下が見えます.反対にkeepVisibleAtStart , ターゲットオブジェクトの上端がスクロールコンテナーの上端より上にある場合、ターゲットオブジェクトの上部が見えるようにします

結論
そしてこれです!私は、あなたがこのポスト役に立つとわかることを望みますScrollable.ensureVisible すべてのオプションをスクロールビュー上の特定のウィジェットにスクロールします.
あなたがこのポストについての質問またはコメントをするならば、私に連絡してください!
次回まで!