1
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 3 years have passed since last update.

Flutter, React Native(Expo), Xamarin.Formsでハンバーガーメニュー(DrawerMenu)の実装を比べる

Last updated at Posted at 2021-01-07

Flutter, React Native(Expo), Xamarin.Formsで、振る舞いが同じアプリを作っているんですが、その時にそれぞれのフレームワークでハンバーガーメニューはどのような実装になるのか調べました。今回はハンバーガーメニューから別画面に遷移できるようにしました。
調べた時に「要点となる実装部分だけの記載」という記事が多くて、初心者にとっては実装する時に「このコードはどこに追加(置き換え)するんだろうか?」って思ったので、FlutterとReact Native(expo)はコピペで動作するように記載しました。Xamarin.Formsはコピペの後でnamespaceを書き換えてください。

留意事項

  • その1

    • 「こう実装すればこう動く、ってことが分かればそれでOK!」を方針に実装を進めた個人開発の内容を記載しましたので、「なぜその実装が必要なのか?」や「なぜその実装で動くのか?」は分かってない部分多数です。。。 :bow:
  • その2

    • 筆者は実戦経験が乏しい(というより、アプリ開発は個人開発を除いて、現場での開発が未経験。。)、なので、ご指摘は「ここは、こうした方が良いよ:wink:」くらいのノリでお願いします。:pray::pray::pray:
  • その3

    • 「HelloWorldはできたけど、他にはこんなことできたら嬉しいな〜」って思っていた時のことを思い出して書きました。そのため、強々エンジニアや超人エンジニアの方々はこのページを参考にしない方が良いと思います。:expressionless::sleepy: :cold_sweat:

※iOSとAndroidのバージョン
iOS-13.3![Android-29(API Level)](https://img.shields.io/badge/Android-29(API Level)-brightgreen)

フレームワークごとの相違点&要点

画面遷移はこの記事(自分の記事)を参考にしてます。

  • Flutter

    • Drawer(ハンバーガーメニュー) + ListView(メニュー内をList表示)で実装
  • React Native (Expo)

    • StackとDrawerをインストール&import
    • Drawer Navigator(ハンバーガーメニュー+その要素)を定義
  • Xamarin.Forms

    • メニュー部分をContentPageとする( → メニューを1つの画面として扱う)
    • メニュー部分のContentPageをMasterDetailPageのMaster(ハンバーガーメニューの土台)に実装する

Flutter

以下の記事を参考に、Flutterでハンバーガーメニューを実装しました。
参照記事
https://flutter.keicode.com/basics/drawer.php
https://dev.classmethod.jp/articles/intro_flutter_widget_drawer/

概要

特にライブラリのインポートは不要で、Flutterの機能のみでハンバーガーメニューと画面遷移を実装できます。
DrawerとListViewをWidgetに追加して、ListViewのListTitleにメニューに表示する項目名を定義します。

画面収録-2021-01-03-16.20.00.gif画面収録-2021-01-03-16.21.00.gif

実装

Flutter-1.20.3

main.dart
import 'package:flutter/material.dart';

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

class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'Flutter Demo',
      theme: ThemeData(
        primarySwatch: Colors.blue,
      ),
      home: MyHomePage(title: 'Flutter Demo Home Page'),
      routes: {
        '/first': (BuildContext context) => MyHomePage(),
        '/second': (BuildContext context) => MySecondPage(),
      },
    );
  }
}

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

  final String title;

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

class _MyHomePageState extends State<MyHomePage> {

  @override
  Widget build(BuildContext context) {
    return new Scaffold(
      appBar: new AppBar(
        title: new Text('TestProject'),
      ),
      drawer: new Drawer(
          child: new ListView(
            children: <Widget> [
              new DrawerHeader(child: new Text('Header'),),
              new ListTile(
                title: new Text('First Menu Item'),
                onTap: () {
                  Navigator.of(context).pushNamed('/first');
                },
              ),
              new ListTile(
                title: new Text('Second Menu Item'),
                onTap: () {
                  Navigator.of(context).pushNamed('/second');
                },
              ),
            ],
          )
      ),
      body: new Center(
        child: new Text(
          'Center',
        ),
      ),
    );
  }
}

class MySecondPage extends StatelessWidget {

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text('2ページ目'),
      ),
      drawer: new Drawer(
          child: new ListView(
            children: <Widget> [
              new DrawerHeader(child: new Text('Header'),),
              new ListTile(
                title: new Text('First Menu Item'),
                onTap: () {
                  Navigator.of(context).pushNamed('/first');
                },
              ),
              new ListTile(
                title: new Text('Second Menu Item'),
                onTap: () {
                  Navigator.of(context).pushNamed('/second');
                },
              ),
            ],
          )
      ),
      body: Center(
      ),
    );
  }
}

React Native(Expo)

以下の記事を参考に、React Native(Expo)でハンバーガーメニューを実装しました。
参照記事
https://qiita.com/zaburo/items/aaf198c79520dbaa48e2
https://react.keicode.com/native/navigation-drawernavigator.php
https://aboutreact.com/react-native-navigation-drawer/

概要

まず、react-navigation/drawer, react-navigation/stackをプロジェクトにインストールします。
NavigationContainerにDrawerNavigatorを実装し、そこへDrawer.Screeenをメニューの要素として実装します。
プロジェクトフォルダの直下に「screen」フォルダを新規作成し、以下のファイルを「screen」フォルダに新規作成します。

  • Screen1.js
  • Screen2.js

画面収録-2021-01-03-16.26.03.gif画面収録-2021-01-03-16.26.54.gif

実装

![React Native cli-2.0.1](https://img.shields.io/badge/React Native cli-2.0.1-brightgreen)Expo-3.23.3
※その他のバージョンは以下の通りです。

    "@react-navigation/drawer": "^5.9.0",
    "@react-navigation/stack": "^5.9.0",
App.js
import 'react-native-gesture-handler';

import * as React from 'react';
import { View, TouchableOpacity, Image } from 'react-native';

import { NavigationContainer } from '@react-navigation/native';
import { createStackNavigator } from '@react-navigation/stack';
import { createDrawerNavigator } from '@react-navigation/drawer';

import FirstPage from './pages/Screen1';
import SecondPage from './pages/Screen2';

const Stack = createStackNavigator();
const Drawer = createDrawerNavigator();

const NavigationDrawerStructure = (props)=> {
  const toggleDrawer = () => {
    props.navigationProps.toggleDrawer();
  };

  return (
    <View style={{ flexDirection: 'row' }}>
      <TouchableOpacity onPress={()=> toggleDrawer()}>
        {/*Donute Button Image */}
        <Image
          source={{uri: 'https://raw.githubusercontent.com/AboutReact/sampleresource/master/drawerWhite.png'}}
          style={{ width: 25, height: 25, marginLeft: 5 }}
        />
      </TouchableOpacity>
    </View>
  );
}

function firstScreenStack({ navigation }) {
  return (
      <Stack.Navigator initialRouteName="FirstPage">
        <Stack.Screen
          name="FirstPage"
          component={FirstPage}
          options={{
            title: 'First Page', //Set Header Title
            headerLeft: ()=> <NavigationDrawerStructure navigationProps={navigation} />,
            headerStyle: {
              backgroundColor: '#f4511e', //Set Header color
            },
            headerTintColor: '#fff', //Set Header text color
            headerTitleStyle: {
              fontWeight: 'bold', //Set Header text style
            },
          }}
        />
      </Stack.Navigator>
  );
}

function secondScreenStack({ navigation }) {
  return (
    <Stack.Navigator
      initialRouteName="SecondPage"
      screenOptions={{
        headerLeft: ()=> <NavigationDrawerStructure navigationProps={navigation} />,
        headerStyle: {
          backgroundColor: '#0460fe', //Set Header color
        },
        headerTintColor: '#fff', //Set Header text color
        headerTitleStyle: {
          fontWeight: 'bold', //Set Header text style
        }
      }}>
      <Stack.Screen
        name="SecondPage"
        component={SecondPage}
        options={{
          title: 'Second Page', //Set Header Title
          
        }}/>
    </Stack.Navigator>
  );
}

function App() {
  return (
    <NavigationContainer>
      <Drawer.Navigator
        drawerContentOptions={{
          activeTintColor: '#e91e63',
          itemStyle: { marginVertical: 5 },
        }}>
        <Drawer.Screen
          name="FirstPage"
          options={{ drawerLabel: 'First page Option' }}
          component={firstScreenStack} />
        <Drawer.Screen
          name="SecondPage"
          options={{ drawerLabel: 'Second page Option' }}
          component={secondScreenStack} />
      </Drawer.Navigator>
    </NavigationContainer>
  );
}

export default App;
Screen1.js
import * as React from 'react';
import { Button, View, Text, SafeAreaView } from 'react-native';

const FirstPage = ({ navigation }) => {
  return (
    <SafeAreaView style={{ flex: 1 }}>
      <View style={{ flex: 1 , padding: 16}}>
        <View
          style={{
            flex: 1,
            alignItems: 'center',
            justifyContent: 'center',
          }}>
          <Text
            style={{
              fontSize: 25,
              textAlign: 'center',
              marginBottom: 16
            }}>
            This is the First Page under First Page Option
          </Text>
        </View>
      </View>
    </SafeAreaView>
  );
}

export default FirstPage;
Screen2.js
import * as React from 'react';
import { Button, View, Text, SafeAreaView } from 'react-native';

const SecondPage = ({ navigation }) => {
  return (
    <SafeAreaView style={{ flex: 1 }}>
      <View style={{ flex: 1, padding: 16 }}>
        <View
          style={{
            flex: 1,
            alignItems: 'center',
            justifyContent: 'center',
          }}>
          <Text
            style={{
              fontSize: 25,
              textAlign: 'center',
              marginBottom: 16
            }}>
            This is Second Page under Second Page Option
          </Text>
        </View>
      </View>
    </SafeAreaView>
  );
}

export default SecondPage;

Xamarin.Forms

以下の記事を参考に、Xamarin.Formsでハンバーガーメニューを実装しました。
参照記事:https://www.atmarkit.co.jp/ait/articles/1611/02/news025_4.html

概要

XAMLのClassでMasterDetailPageを作ります。MasterDetailPageのMasterにメニュー画面を「ContentPageとして」追加します。メニュー画面(ContentPageの方)にListViewで要素を追加します。

_2021-01-03 16.33.11.gif_2021-01-03 16.33.59.gif

実装

Xamarin.Forms-4.6.0.1141

App.xaml.cs
using Xamarin.Forms;

namespace xamarin.forms_project
{
    public partial class App : Application
    {
        public App()
        {
            InitializeComponent();

            MainPage = new MyMasterDetailPage();
        }

        protected override void OnStart()
        {
        }

        protected override void OnSleep()
        {
        }

        protected override void OnResume()
        {
        }
    }
}
MyMasterDetailPage.xaml
<?xml version="1.0" encoding="utf-8" ?>
<MasterDetailPage xmlns="http://xamarin.com/schemas/2014/forms"
                  xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
                  xmlns:local="clr-namespace:xamarin.forms_project;assembly=xamarin.forms_project"
                  x:Class="xamarin.forms_project.MyMasterDetailPage">
    <MasterDetailPage.Master>
        <local:MyMasterPage x:Name="masterPage" />
    </MasterDetailPage.Master>
    <MasterDetailPage.Detail>
        <NavigationPage>
            <x:Arguments>
                <local:FirstPage />
            </x:Arguments>
        </NavigationPage>
    </MasterDetailPage.Detail>
</MasterDetailPage>
MyMasterDetailPage.cs
using System;
using Xamarin.Forms;

namespace xamarin.forms_project
{
    public partial class MyMasterDetailPage : MasterDetailPage
    {
        public MyMasterDetailPage()
        {
            InitializeComponent();
            masterPage.ListView.ItemSelected += OnItemSelected;
        }

        void OnItemSelected(object sender, SelectedItemChangedEventArgs e)
        {
            var item = e.SelectedItem as MyMasterPageItem;
            if (item != null)
            {
                Detail = new NavigationPage((Page)Activator.CreateInstance(item.TargetType));
                masterPage.ListView.SelectedItem = null;
                IsPresented = false;
            }
        }
    }
}
MyMasterPage.xaml
<?xml version="1.0" encoding="utf-8" ?>
<ContentPage xmlns="http://xamarin.com/schemas/2014/forms"
             xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
             x:Class="xamarin.forms_project.MyMasterPage"
             Padding="0,40,0,0"
             Icon="hanburger.png"
             Title="メニュー">
    <StackLayout VerticalOptions="FillAndExpand">
        <Frame BackgroundColor="#2196F3" Padding="24" CornerRadius="0">
            <Label Text="Menu" HorizontalTextAlignment="Center" TextColor="White" FontSize="16" />
        </Frame>
        <ListView x:Name="listView" VerticalOptions="FillAndExpand" SeparatorVisibility="None">
            <ListView.ItemTemplate>
                <DataTemplate>
                    <ImageCell Text="{Binding Title}" ImageSource="{Binding IconSource}" />
                </DataTemplate>
            </ListView.ItemTemplate>
        </ListView>
    </StackLayout>
</ContentPage>
MyMasterPage.xaml.cs
using System.Collections.Generic;
using Xamarin.Forms;

namespace xamarin.forms_project
{
    public partial class MyMasterPage : ContentPage
    {
        public MyMasterPage()
        {
            InitializeComponent();

            var myMasterPageItems = new List<MyMasterPageItem>();
            myMasterPageItems.Add(new MyMasterPageItem
            {
                Title = "1枚目",
                IconSource = "icon.png",
                TargetType = typeof(FirstPage)
            });
            myMasterPageItems.Add(new MyMasterPageItem
            {
                Title = "2枚目",
                IconSource = "icon.png",
                TargetType = typeof(SecondPage)
            });
            listView.ItemsSource = myMasterPageItems;
        }
        public ListView ListView { get { return listView; } }
    }
}
SecondPage.xaml
<?xml version="1.0" encoding="UTF-8"?>
<ContentPage
    xmlns="http://xamarin.com/schemas/2014/forms"
    xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
    x:Class="xamarin.forms_project.SecondPage">
    <StackLayout x:Name="Layout">
        <Image Source="https://picsum.photos/250?image=9" HeightRequest="500" WidthRequest="500" />
    </StackLayout>
</ContentPage>
SecondPage.xaml.cs
using Xamarin.Forms;

namespace xamarin.forms_project
{
    public partial class SecondPage : ContentPage
    {
        public SecondPage()
        {
            InitializeComponent();
        }
    }
}
MyMasterPageItem.cs
using System;

namespace xamarin.forms_project
{
    public class MyMasterPageItem
    {
        public string Title { get; set; }
        public string IconSource { get; set; }
        public Type TargetType { get; set; }
    }
}

感想

画面遷移の記事と同じく、個人的にはXamarin.Formsがわかりやすかったかな〜と。

1
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
1
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?