Flutter, React Native(Expo), Xamarin.Formsでタップした画像を変更する実装を比べる

Flutter, React Native(Expo), Xamarin.Formsで、振る舞いが同じアプリを作っているんですが、その時にそれぞれのフレームワークで画像をタップしてその画像を動的に変更する場合にはどのような実装になるのか調べました。
調べた時に「要点となる実装部分だけの記載」という記事が多くて、初心者にとっては実装する時に「このコードはどこに追加(置き換え)するんだろうか？」って思ったので、FlutterとReact Native(expo)はコピペで動作するように記載しました。Xamarin.Formsはコピペの後でnamespaceを書き換えてください。
今回取り上げることの他には、画像をタップしてその画像をモーダル表示する時などに役立つと思い。。。

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

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

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

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

# Flutter

以下の記事を参考に、Flutterで画像をタップする実装を行いました。
参照記事：https://qiita.com/fujit33/items/5094efcedbe49331168e

## 概要

画像をタップする場合、GestureDetectorを使います。GestureDetectorはFlutterで用意されている(?)ため、pubspec.yamlのdependenciesへ追加は不要です。
なお、画像はassetsフォルダ配下に「images」フォルダを新規作成し、imagesフォルダに配置しました。

## 実装
![Flutter-1.20.3](https://img.shields.io/badge/Flutter-1.20.3-brightgreen)

```yaml:pubspec.yaml
name: flutterproject
description: A new Flutter application.

version: 1.0.0+1

environment:
  sdk: ">=2.1.0 <3.0.0"

dependencies:
  flutter:
    sdk: flutter

  cupertino_icons: ^0.1.2

dev_dependencies:
  flutter_test:
    sdk: flutter


flutter:

  uses-material-design: true

# 画像を配置したフォルダを設定
# 今回はassets配下に「images」フォルダを新規作成しました
  assets:
    - assets/images/
```


```dart: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: 'Change Image'),
    );
  }
}
class MyHomePage extends StatefulWidget {
  MyHomePage({Key key, this.title}) : super(key: key);
  final String title;
  @override
  _MyHomePageState createState() => _MyHomePageState();
}
class _MyHomePageState extends State<MyHomePage> {

  final String path = 'assets/images/image00';
  String imagePath = 'assets/images/image001.jpg';
  int count = 1;
  final int maxCount = 3;

  void changeImage(){
    count++;

    if(maxCount < count){
      count = 1;
    }

    setState(() {
      imagePath = path + count.toString() + '.jpg';
    });
  }
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text(widget.title),
      ),
      body: GestureDetector(
        //タップした時のイベント処理を実装する
        onTap: () => changeImage(),
        //GestureDetectorの子要素で画像を実装する
        child: Image.asset(imagePath),
      ),
    );
  }
}
```

# React Native(Expo)
以下の記事を参考に、React Native(Expo)で画像をタップする実装を行いました。
参照記事：https://bagelee.com/programming/react-native/touchableopacity-react-native/

## 概要

画像をタップする場合、TouchableOpacityを使います。TouchableOpacityはReact Nativeで用意されている(?)ため、ライブラリの追加不要です。(ホッ、よかった〜！)
た・だ・し、！
プロジェクトのローカル環境にある画像をimageのrequireへ動的に実装することができないので、
参考：https://stackoverflow.com/questions/30854232/react-native-image-require-module-using-dynamic-names

1. 初期設定としてのrequireを設定する専用のjsファイル「ImapgePath.js」を新規作成
	1. ImapgePath.jsに利用する画像の枚数分だけ、requireを作成
	1. 今回はImapgePath.jsに配列でrequireを作成
1. ImapgePath.jsをApp.jsからimport
1. 画像を変更する際にはApp.jsからImapgePath.jsの配列にあるrequireを参照(?)

します。
なお、画像はassetsフォルダ配下に「images」フォルダを新規作成し、imagesフォルダに配置しました。

## 実装
![React Native cli-2.0.1](https://img.shields.io/badge/React Native cli-2.0.1-brightgreen)![Expo-3.23.3](https://img.shields.io/badge/Expo-3.23.3-brightgreen)

```javascript:ImagePath.js
const images = {
    items:[
        {req : require('./assets/images/image001.jpg')},
        {req : require('./assets/images/image002.jpg')},
        {req : require('./assets/images/image003.jpg')},
    ]
  }
  
export { images };
```

```javascript:App.js
import React from 'react';
import { SafeAreaView, StyleSheet, TouchableOpacity, Image } from 'react-native';
import { images } from './ImagePath';  //requireを参照するためのファイルをimport

export default class App extends React.Component {

  state = {
    imageCount: 0
  };

  onPress = () => {
    var countNum = 0;
    if(this.state.imageCount < 2){
      countNum = this.state.imageCount + 1;
    }
    this.setState({
      imageCount: countNum
    });
  }

  render() {
    return (
      <SafeAreaView style={styles.container}>
        // タップするコンポーネント
        <TouchableOpacity
          onPress={this.onPress} //タップした時の処理を実装
        >
          <Image
          style={styles.image}
          // ImagePath.jsの配列にあるrequireを参照
          source={images.items[this.state.imageCount].req}
          />
        </TouchableOpacity>
      </SafeAreaView>
    );
  }
}

const styles = StyleSheet.create({
  container: {
    flex: 1,
    marginTop: 100,
    marginLeft: 100,
  },
  image: {
    width: 200,
    height: 100,
    marginLeft: 10,
    marginTop: 10,
  }
});
```

# Xamarin.Forms
以下の記事を参考に、Xamarin.Formsで画像をタップする実装を行いました。
参照記事：https://docs.microsoft.com/ja-jp/xamarin/xamarin-forms/app-fundamentals/gestures/tap

## 概要

画像をタップする場合、TapGestureRecognizerを使います。
画像は「images」フォルダを新規作成し、imagesフォルダに配置しました。
配置方法は、imagesフォルダに画像の追加(imagesフォルダを右クリック)で別ディレクトリにある画像をコピー追加しました。
た・だ・し、！

1. 画像１枚ずつにビルドアクション[EmbeddedResource]を設定
	1. 参考：https://docs.microsoft.com/ja-jp/xamarin/xamarin-forms/user-interface/images?tabs=macos#display-images
1. 画像１枚ずつのリソースIDを取得
1. ローカル画像をコードビハインド(xaml.csファイル)で、ImageのImageSourceにリソースIDを設定
	1. 参考：https://shuhelohelo.hatenablog.com/entry/2020/01/05/131503
1. AndroidでXamarin.Formsにある画像をリンク設定
	1. [プロジェクト名].Android配下にある「Resources/drawable」にimagesフォルダごとリンクを設定
1. iOSでXamarin.Formsにある画像をリンク設定
	1. [プロジェクト名].iOS配下にある「Resources」にimagesフォルダごとリンクを設定
		1. 参考：https://stackoverflow.com/questions/54090850/how-to-set-an-image-from-embedded-resource-using-xaml-in-xamarin-forms

というのが必要でして、以下のコードをコピペした後で、上記の設定を行わないと、iOS / Android共に画像を表示しません、、（ハァ〜）

## 実装
![Xamarin.Forms-4.6.0.1141](https://img.shields.io/badge/Xamarin.Forms-4.6.0.1141-brightgreen)

```xml:MainPage.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"
    xmlns:ios="clr-namespace:Xamarin.Forms.PlatformConfiguration.iOSSpecific;assembly=Xamarin.Forms.Core" 
    ios:Page.UseSafeArea="true"
    x:Class="xamarin.forms_project.MainPage">
    <StackLayout x:Name="Layout">
        <Image
            x:Name="image"
            HorizontalOptions="Center"
            VerticalOptions="Center"
            >
            <Image.GestureRecognizers>
                <TapGestureRecognizer
                    Tapped="OnTapGestureRecognizerTapped"
                    NumberOfTapsRequired="1" />
            </Image.GestureRecognizers>
        </Image>
    </StackLayout>
</ContentPage>
```

```C#:MainPage.xaml.cs
using System;
using Xamarin.Forms;

namespace xamarin.forms_project
{
    public partial class MainPage : ContentPage
    {
        int index = 1;
        public MainPage()
        {
            InitializeComponent();
            image.Source = ImageSource.FromResource("xamarin.forms_project.images.image" + String.Format("{0:D3}", index) + ".jpg");
        }
        void OnTapGestureRecognizerTapped(object sender, EventArgs args)
        {
            index++;
            if (index == 4)
            {
                index = 1;
            }
            image.Source = ImageSource.FromResource("xamarin.forms_project.images.image" + String.Format("{0:D3}", index) + ".jpg");
        }
    }
}
```

# 感想
React Native(Expo)とXamarin.Formsは「ローカルにある何かをプロジェクトに取り込む」には今後も苦労しそうだ。。。:smirk: