Flutter, React Native(Expo), Xamarin.Formsで、振る舞いが同じアプリを作っているんですが、その時にそれぞれのフレームワークでハンバーガーメニューはどのような実装になるのか調べました。今回はハンバーガーメニューから別画面に遷移できるようにしました。
調べた時に「要点となる実装部分だけの記載」という記事が多くて、初心者にとっては実装する時に「このコードはどこに追加(置き換え)するんだろうか?」って思ったので、FlutterとReact Native(expo)はコピペで動作するように記載しました。Xamarin.Formsはコピペの後でnamespaceを書き換えてください。
留意事項
-
その1
- 「こう実装すればこう動く、ってことが分かればそれでOK!」を方針に実装を進めた個人開発の内容を記載しましたので、「なぜその実装が必要なのか?」や「なぜその実装で動くのか?」は分かってない部分多数です。。。
-
その2
- 筆者は実戦経験が乏しい(というより、アプリ開発は個人開発を除いて、現場での開発が未経験。。)、なので、ご指摘は「ここは、こうした方が良いよ」くらいのノリでお願いします。
-
その3
- 「HelloWorldはできたけど、他にはこんなことできたら嬉しいな〜」って思っていた時のことを思い出して書きました。そのため、強々エンジニアや超人エンジニアの方々はこのページを参考にしない方が良いと思います。
※iOSとAndroidのバージョン
![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にメニューに表示する項目名を定義します。
実装
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
実装
![React Native cli-2.0.1](https://img.shields.io/badge/React Native cli-2.0.1-brightgreen)
※その他のバージョンは以下の通りです。
"@react-navigation/drawer": "^5.9.0",
"@react-navigation/stack": "^5.9.0",
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;
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;
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で要素を追加します。
実装
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()
{
}
}
}
<?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>
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;
}
}
}
}
<?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>
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; } }
}
}
<?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>
using Xamarin.Forms;
namespace xamarin.forms_project
{
public partial class SecondPage : ContentPage
{
public SecondPage()
{
InitializeComponent();
}
}
}
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がわかりやすかったかな〜と。