質問をすることでしか得られない、回答やアドバイスがある。

15分調べてもわからないことは、質問しよう!

ただいまの
回答率

88.63%

[Flutter][iPad][UI] 左側のサイドバーを固定し、右側画面のみで遷移を行いたい

受付中

回答 2

投稿 編集

  • 評価
  • クリップ 0
  • VIEW 514

inami_173

score 22

現状・問題点

現在FlutterでiPad専用のアプリケーションを開発しています。
UIはiPadの設定アプリのようなものを作成したいと思っているのですが、うまくいっていないので質問させていただきます。

iPadの設定アプリのUIはこちらです。

このように左側にサイドバーがあり、右側にその内容を表示するというところまでは出来ているのですが(画像)、右側でNavigator.of.pushを用いた遷移を行うと、サイドバーの上に遷移先の画面が覆いかぶさってしまってしまいます(画像)。

サイドバーを固定したままで遷移させたいのですが、やり方を知っている方がいたら教えていただきたいです🙇🏻‍♂️

コード

lib/main.dart

import 'package:flutter/material.dart';
import 'package:health_check/ui/master_detail_container.dart';

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

class MyAppScreen extends StatefulWidget {
  @override
  _MyAppScreenState createState() => _MyAppScreenState();
}

class _MyAppScreenState extends State<MyAppScreen> {
  @override
  Widget build(BuildContext context) {
    return  MaterialApp(
      home: MasterDetailContainer(),
    );
  }
}

lib/ui/master_detail_container.dart

import 'package:flutter/material.dart';
import 'package:health_check/ui/item.dart';
import 'package:health_check/ui/item_details.dart';
import 'package:health_check/ui/item_listing.dart';

class MasterDetailContainer extends StatefulWidget {
  @override
  _ItemMasterDetailContainerState createState() =>
      _ItemMasterDetailContainerState();
}

class _ItemMasterDetailContainerState extends State<MasterDetailContainer> {
  Item _selectedItem;

  Widget _sideBar() {
    return Flexible(
      flex: 1,
      child: Material(
        elevation: 4.0,
        child: ItemListing(
          itemSelectedCallback: (item) {
            setState(() {
              _selectedItem = item;
            });
          },
          selectedItem: _selectedItem,
        ),
      ),
    );
  }

  Widget _itemContent() {
    return Flexible(
      flex: 3,
      child: ItemDetails(
        item: _selectedItem,
      ),
    );

  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      body: Row(
        children: <Widget>[
          _sideBar(),
          _itemContent(),
        ],
      ),
    );
  }
}

lib/ui/item_listing.dart

import 'package:flutter/material.dart';
import 'package:health_check/ui/item.dart';
import 'package:health_check/ui/item_details.dart';

class ItemListing extends StatelessWidget {
  ItemListing({
    @required this.itemSelectedCallback,
    this.selectedItem,
  });

  final ValueChanged<Item> itemSelectedCallback;
  final Item selectedItem;

  @override
  Widget build(BuildContext context) {
    // return
    return ListView(
      children: items.map((item) {
        return ListTile(
          title: Text(item.title),
          onTap: () => itemSelectedCallback(item),
          selected: selectedItem == item,
        );
      }).toList(),
    );
  }
}

lib/ui/item_details.dart

import 'package:flutter/material.dart';
import 'package:health_check/ui/item.dart';
import 'package:health_check/ui/item1_screen.dart';
import 'package:meta/meta.dart';

class ItemDetails extends StatelessWidget {
  ItemDetails({
    @required this.item,
  });

  final Item item;

  @override
  Widget build(BuildContext context) {
    final TextTheme textTheme = Theme.of(context).textTheme;

    final Widget content = Column(
      mainAxisAlignment: MainAxisAlignment.center,
      children: [
        RaisedButton(
          child: Text("Button"),
          color: Colors.orange,
          textColor: Colors.white,
          onPressed: () {
            Navigator.of(context, rootNavigator: true).push(
                MaterialPageRoute(builder: (context) => Item1Screen()));
          },
        ),
        Text(
          item?.title ?? 'No item selected!',
          style: textTheme.headline,
        ),
        Text(
          item?.subtitle ?? 'Please select one on the left.',
          style: textTheme.subhead,
        ),
      ],
    );

    return Scaffold(
      appBar: AppBar(
        title: Text('Appbar'),
      ),
      body: Center(child: content),
    );
  }
}

lib/ui/item1_screen.dart

import 'package:flutter/material.dart';

class Item1Screen extends StatelessWidget {
  Item1Screen();

  @override

  Widget build(BuildContext context){
    return Scaffold(
        appBar: AppBar(
          title: Text('Item1 detail'),
        ),
        body: Center()
    );
  }
}

lib/ui/item.dart

import 'package:meta/meta.dart';

class Item {
  Item({
    @required this.title,
    @required this.subtitle,
  });

  final String title;
  final String subtitle;
}

final List<Item> items = <Item>[
  Item(
    title: 'Item 1',
    subtitle: 'This is the first item.',
  ),
  Item(
    title: 'Item 2',
    subtitle: 'This is the second item.',
  ),
  Item(
    title: 'Item 3',
    subtitle: 'This is the third item.',
  ),
];
  • 気になる質問をクリップする

    クリップした質問は、後からいつでもマイページで確認できます。

    またクリップした質問に回答があった際、通知やメールを受け取ることができます。

    クリップを取り消します

  • 良い質問の評価を上げる

    以下のような質問は評価を上げましょう

    • 質問内容が明確
    • 自分も答えを知りたい
    • 質問者以外のユーザにも役立つ

    評価が高い質問は、TOPページの「注目」タブのフィードに表示されやすくなります。

    質問の評価を上げたことを取り消します

  • 評価を下げられる数の上限に達しました

    評価を下げることができません

    • 1日5回まで評価を下げられます
    • 1日に1ユーザに対して2回まで評価を下げられます

    質問の評価を下げる

    teratailでは下記のような質問を「具体的に困っていることがない質問」、「サイトポリシーに違反する質問」と定義し、推奨していません。

    • プログラミングに関係のない質問
    • やってほしいことだけを記載した丸投げの質問
    • 問題・課題が含まれていない質問
    • 意図的に内容が抹消された質問
    • 過去に投稿した質問と同じ内容の質問
    • 広告と受け取られるような投稿

    評価が下がると、TOPページの「アクティブ」「注目」タブのフィードに表示されにくくなります。

    質問の評価を下げたことを取り消します

    この機能は開放されていません

    評価を下げる条件を満たしてません

    評価を下げる理由を選択してください

    詳細な説明はこちら

    上記に当てはまらず、質問内容が明確になっていない質問には「情報の追加・修正依頼」機能からコメントをしてください。

    質問の評価を下げる機能の利用条件

    この機能を利用するためには、以下の事項を行う必要があります。

回答 2

+1

BottomNavigationBarを各画面で共有する方法ならば、英語ですが以下の記事によくまとめられています。読めるのであればぜひ読んでください。

https://medium.com/coding-with-flutter/flutter-case-study-multiple-navigators-with-bottomnavigationbar-90eb6caa6dbf

ただ、この質問の場合、オリジナルのサイドバーなので同じようにはいかない可能性があります。
個人的には以下のようなアイデアが思い浮かびます。

1.Navigatorによる画面遷移を使わず、ステートによって以下のitemContent()の内容を切り替える。

body: Row(
  children: <Widget>[
    sideBar(),
    itemContent(),
  ],
),

2.画面遷移先にも同じようなサイドバーを設ける。
なんとなくこれはやりたくないですね...

ほかにもなにか思いついたら追記します。

投稿

編集

  • 回答の評価を上げる

    以下のような回答は評価を上げましょう

    • 正しい回答
    • わかりやすい回答
    • ためになる回答

    評価が高い回答ほどページの上位に表示されます。

  • 回答の評価を下げる

    下記のような回答は推奨されていません。

    • 間違っている回答
    • 質問の回答になっていない投稿
    • スパムや攻撃的な表現を用いた投稿

    評価を下げる際はその理由を明確に伝え、適切な回答に修正してもらいましょう。

  • 2020/06/25 17:24

    回答ありがとうございます。

    BottomNavigationBarは以前使ったことがあるのでなんとなくわかっているのですが、やっぱ今回のようなケースはオリジナルで作る必要があるんですね…。

    1.そうですね。itemContent()の方で遷移するときにNavigator.of.push()のようなアニメーションを右半分だけに対して行う、みたいなことをする必要があるんですかね。。
    正直ちょっとコストかかりそうですね🤔

    2.これも思いついたのですが、右側からサイドバーもまとめてスワイプして出てくるのはどうも気持ち悪くて自分の中で無しになりました。


    > ほかにもなにか思いついたら追記します。

    ありがとうございます。
    よろしくおねがいします🙇🏻‍♂️

    キャンセル

  • 2020/06/30 11:09

    私なら、画面右半分の部分をPageViewにして、アニメーションはカスタマイズします。
    どんなアニメーションを使いたいかで変わってくると思いますが、PageViewを使えばデフォルトでアニメーションがついていますし、ある程度簡単にカスタマイズも可能だと思います。

    キャンセル

+1

限られた領域内で遷移を行いたい場合は、Navigator の範囲を限定してあげる必要があると思います。

その場合の実装方法は2つです。

  1. MaterialApp が提供する Navigator とは別に、新たな Navigator を用意して遷移させたい領域を囲む。
  2. MaterialApp が提供する Navigator の範囲を限定する。
1. MaterialApp が提供する Navigator とは別に、新たな Navigator を用意して遷移させたい領域を囲む。

新たな Navigator を用意する場合は、Navigator をそのまま使用するのでも良いですし、同じ要件を満たすことのできる CupertinoTabView を流用する方法もあります。

今回は、 コード量が少なく済む CupertinoTabView を使用する例を載せます。

Widget _itemContent() {
  return Flexible(
    flex: 3,
    child: CupertinoTabView(
      builder: (_context) => ItemDetails(
        item: _selectedItem,
      ),
    ),
  );
}
2. MaterialApp が提供する Navigator の範囲を限定する。

MaterialApp の builder 引数を使用すれば、 Navigator よりも上層で Widget を構成できます。(そのため、Navigator の領域を可変させることも可能)

以下のようになります。

MaterialApp(
  home: MyHomePage(),
  builder: (context, child) {
    return Row(
      children: <Widget>[
        /// _sideBar() に相当する Widget を配置する
        SideBar(),
        Expanded(child: child),
      ],
    );
  },
);

但し、こちらの方法を用いる場合は、以下の点に十分注意してください。

  1. MaterialApp を build する Widget 上では、setState を呼ぶとリビルド範囲が大きくなるので避けた方が良い。Provider などを使用すると良いと思います。
  2. MaterialApp#builder 内では MediaQuery は取得できないのでMediaQuery を使用してサイドバーの表示を可変させたい場合は使えません。質問内容では、今回は iPad 専用とのことだったのですが、今後そのような要件が出てきた場合には注意が必要です。

投稿

編集

  • 回答の評価を上げる

    以下のような回答は評価を上げましょう

    • 正しい回答
    • わかりやすい回答
    • ためになる回答

    評価が高い回答ほどページの上位に表示されます。

  • 回答の評価を下げる

    下記のような回答は推奨されていません。

    • 間違っている回答
    • 質問の回答になっていない投稿
    • スパムや攻撃的な表現を用いた投稿

    評価を下げる際はその理由を明確に伝え、適切な回答に修正してもらいましょう。

15分調べてもわからないことは、teratailで質問しよう!

  • ただいまの回答率 88.63%
  • 質問をまとめることで、思考を整理して素早く解決
  • テンプレート機能で、簡単に質問をまとめられる

関連した質問

同じタグがついた質問を見る