Help us understand the problem. What is going on with this article?

Re:0から始めるAndroid開発 ~いろんなフレームワークを使ってみる(8/25完了)~

はじめに

この記事は Android Advent Calendar 2019 の6日目です!

普段、Androidアプリケーションをどの言語どのフレームワークetc..を使って開発していますか?
自分は Android Studio + JavaUnity + C# で開発することが多いです
思えば自分が触ったことが無いフレームワークはたくさんありますが、自分が開発したいもの合わせて、よりベストな環境を選んでおきたい身としてはいろんなフレームワーク、言語を知って触っておくに越したことは無いのはエンジニアとしては当然だと思ってます。
なので、いろんなフレームワークを使って 同じ動作をする Androidアプリを作成します!

TL;DR

Androidアプリケーション開発のフレームワーク一覧

フレームワーク 言語 公式ページ IDE 一言特徴 実装済(死に戻り)
Android Studio Java/Kotlin/C++ https://developer.android.com/studio/ Android Studio ネイティブアプリケーション
Unity C# https://unity3d.com/jp/unity Unity Editor ゲームエンジン
Processing Java https://processing.org/ Processing グラフィックフレームワーク
openFrameworks C++ https://openframeworks.cc/ Android Studio グラフィックフレームワーク
Cordova HTML5+JavaScript+CSS https://cordova.apache.org/ Visual Studio(Win) クロスプラットフォーム
React Native Javascript/TypeScript https://facebook.github.io/react-native/ 特になし クロスプラットフォーム
Xamarin.Form C#/F# https://dotnet.microsoft.com/apps/xamarin Visual Studio クロスプラットフォーム
Xamarin.Android C#/F# https://dotnet.microsoft.com/apps/xamarin Visual Studio ネイティブアプリケーション
Unreal Engine C++/UnrealScript https://www.unrealengine.com/ Unreal Editor ゲームエンジン
Flutter Dart https://flutter.dev/ Android Studio クロスプラットフォーム
Cocos2d-x C++/Lua/JavaScript/TypeScript https://www.cocos.com/en/ Cocos Creator ゲームエンジン
Monaca HTML5+JavaScript https://ja.monaca.io/ ブラウザ/Monaca Localkit クロスプラットフォーム
DXライブラリ C++ https://dxlib.xsrv.jp/ Android Studio ゲームエンジン
Qt C++ https://www.qt.io/ Qt Creator IDE クロスプラットフォーム
ionic JavaScript https://ionicframework.com/ Ionic Studio クロスプラットフォーム
Titanium JavaScript https://www.appcelerator.com/Titanium/ Appcelerator Studio クロスプラットフォーム
NativeScript TypeScript/JavaScript https://www.nativescript.org/ VSCode/WebStorm クロスプラットフォーム
Weex JavaScript https://weex.apache.org/ 特になし クロスプラットフォーム
meteor JavaScript https://www.meteor.com/ 特になし クロスプラットフォーム
Vue Native JavaScript https://vue-native.io/ 特になし クロスプラットフォーム
Defold Lua https://defold.com/ Defold editor ゲームエンジン
Godot GDScript https://godotengine.org/ Godot Engine ゲームエンジン
SCADE Swift https://www.scade.io/ Scade IDE クロスプラットフォーム
Kotlin Native Kotlin https://kotlinlang.org/docs/reference/native-overview.html Android Studio クロスプラットフォーム
AndEngine Java http://www.andengine.org/ IntelliJ IDEA ゲームエンジン

作るもの

画面にただHello Worldと表示しても面白くないので、ボタンを押すと背景の色が変わるアプリを作ります。
UI要素がどのように設置できるか ボタンイベントをどのように取得できるか Viewはどのように描画できるか 、という点をみれるので良いチュートリアルだと思います
output.gif

Android Studio

Android

  • Google謹製のAndroidプラットフォーム向けアプリ開発用の統合開発環境
  • この環境が出るまでは、Eclipse + ADT (Android Developer Tools)でAndroidアプリの開発が行われていた
  • なにより公式、ドキュメントもツールも揃っている
  • ネイティブアプリケーションを作りたいなら王道

Java

まずは、JavaでAndroidアプリの作成を行います

  1. Android Studioのインストール(説明は割愛します

  2. Android Studioを立ち上げて、今回は新しくアプリを作成するのでStart a new Android Studio projectを選びます。最近開いたプロジェクトは左に表示されています
    alt

  3. ボタンを押したら色が変わるだけのアプリなのでEmpty Activityを選択します
    image.png

  4. 必要な情報を入力します。 Name はアプリ名を入れます。 Save Location はプロジェクトを設置するディレクトリパスです。(既にプロジェクトが存在する場合は already exists... と警告してくれます
    image.png

Finishを押すとAndroidアプリのプロジェクトが生成されます
生成されたファイルはざっと以下の感じになります

androidアプリプロジェクト
AndroidStuidoJava
├── app
│   ├── build.gradle
│   └── src
│       └── main
│           ├── AndroidManifest.xml
│           ├── java
│           │   └── jp
│           │       └── cha84rakanal
│           │           └── hellotogglecolor_studio_java
│           │               └── MainActivity.java
│           └── res
│               ├── drawable
│               │   └── ic_launcher_background.xml
│               ├── layout
│               │   └── activity_main.xml
│               └── values
│                   ├── colors.xml
│                   ├── strings.xml
│                   └── styles.xml
├── build.gradle
├── gradle.properties
├── local.properties
└── settings.gradle

4.プロジェクトが生成されたらボタン実装する前に兎にも角にも一旦起動します。起動には上部の緑の▶マークを押すか、Ctrl + Rを押します。その後、インストールするデバイスを選んでOKを押すと、ビルドされインストールされ起動します。
image.png
image.png

ボタンを実装

アプリが動くことを確認できたらボダンを実装していきます

res/layout/activity_main.xml を開いて次の内容に変更します。するとボタンが画面中央に配置された画面が構成されます

activity_main.xml
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout
    xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    tools:context=".MainActivity">

    <Button
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_centerInParent="true"
        android:text="Hello World!" />

</RelativeLayout>

ボタンがクリックしたことを検知

設置したボタンはこのままではただのボタンなので、プログラムでボタンが押されたことを確認できるようにします。

MainActivity.java に引数にViewクラスを1個を持ち返り値voidの適当なメソッドを作成します

MainActivity.java
package jp.cha84rakanal.hellotogglecolor_studio_java;

import android.app.Activity;
import android.os.Bundle;
import android.util.Log;
import android.view.View;

public class MainActivity extends Activity {

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
    }

    void onButtonClick(View v){
        Log.v(MainActivity.class.getSimpleName(),"onButtonClick");
    }
}

Log.v はログを出力するメソッドでボタンを押すと onButtonClickとログに表示されます
ボタンを押したらどのメソッドを実行するか、今はわからないので、 activity_main.xmlで指定しますButton 要素に android:onClickプロパティを追加して作成した適当なメソッド名を渡します

activity_main.xml
    <Button
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_centerInParent="true"
        android:onClick="onButtonClick"
        android:text="Hello World!" />

これで、ボタンを押すとログにonButtonClickと表示するアプリになりました
ログはターミナルで、adb logcatコマンドを実行するか、Android StudioのLogcatを使います
Logのタグに MainActivity.class.getSimpleName() を指定しているのでフィルターにMainActivityを打つと、onButtonClickのログだけを見れます
※ターミナルでのフィルタリング adb logcat -s MainActivity:*
image.png

背景の色を変更

残りは背景の色を変えるだけです
activity_main.xmlRelativeLayout 要素に android:backgroundプロパティを追加して色を指定します。色の指定は#[透明度00~FF][赤00~FF][緑00~FF][青00~FF]です
MainActivity.javaから参照できるように、IDをつけておきます

activity_main.xml
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    android:id="@+id/back"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:background="#FFFF0000"
    tools:context=".MainActivity">

MainActivity.javaでは、findViewByIdを使って、先程IDをつけた要素を取得して setBackgroundColorを使って色を変える部分を実装します。

MainActivity.java
package jp.cha84rakanal.hellotogglecolor_studio_java;

import android.app.Activity;
import android.os.Bundle;
import android.util.Log;
import android.view.View;

public class MainActivity extends Activity {

    private boolean mToggle = true;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
    }

    void onButtonClick(View v){
        Log.v(MainActivity.class.getSimpleName(),"onButtonClick");
        mToggle = !mToggle;
        findViewById(R.id.back).setBackgroundColor( mToggle? 0xFFFF0000 : 0xFF0000FF);
    }
}

これでボタンを押すと 赤から青、青から赤に色が変わるアプリができました!
output.gif

Kotlin

引き続いて、Android StudioでKotlinを使って同じアプリを作っていきます
Android Studioでは、プロジェクトの中に モジュール という単位で、アプリのプロジェクトやライブラリなどのプロジェクトが存在します。(デフォルトだとプロジェクトを作成した段階でのアプリのモジュール名は app )
次は新しくプロジェクトを作らずに現在のプロジェクトにKotlinのアプリのモジュールを追加します

  1. File > New > New Module... を開き、アプリなので Phone & Tablet Moduleを選択します
    image.png

  2. 必要な情報を入力します。Module Nameはモジュールの名前です。
    image.png

  3. Javaのときと同じくEmpty Activityを選択します
    image.png

  4. Source LanguageをKotlinにします。選択するだけでJavaもKotlinも使えるのはAndroid Studio素晴らしい!
    image.png

Finishを押すとAndroidアプリのモジュールが生成されます
先程作ったプロジェクトの中に、hellotogglecolor_studio_kotlin ディレクトリができてると思います

androidアプリプロジェクト
AndroidStuidoJava
├── app <-- さっきのJavaでのアプリ
├── hellotogglecolor_studio_kotlin <-- これから作るKotlinのアプリ
├── build.gradle
├── gradle.properties
├── local.properties
└── settings.gradle

ただ色が変わるだけのアプリでは、JavaとKotlinでの書き方がちょっと違うぐらいなので、別の方法で実装すすめていきます

ボタンを実装

すべてMainActivity.ktで実装します
MainActivityにおけるルートViewはConstraintLayoutでその子にButtonがあり、センタリングは上下左右の制約で行います。

制約をKotlinで書くと凄い大変なので、極力XMLでレイアウトを組むのが良さそうです...

MainActivity.kt
package jp.cha84rakanal.hellotogglecolor_studio_kotlin

import android.app.Activity
import android.os.Bundle
import androidx.constraintlayout.widget.ConstraintLayout
import androidx.constraintlayout.widget.ConstraintSet
import android.widget.Button
import android.view.View

class MainActivity : Activity() {

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)

        val cLayout = ConstraintLayout(this)
        val button = Button(this)
        cLayout.addView(button)

        val viewId = View.generateViewId()
        button.id =  viewId
        button.text = "Hello World!"

        val constraintSet = ConstraintSet()
        constraintSet.clone(cLayout)

        // android:layout_width="wrap_content"
        constraintSet.constrainHeight(button.id,
                ConstraintSet.WRAP_CONTENT)

        //android:layout_height="wrap_content"
        constraintSet.constrainWidth(button.id,
                ConstraintSet.WRAP_CONTENT)

        // app:layout_constraintBottom_toBottomOf="parent"
        constraintSet.connect(
                button.id,
                ConstraintSet.BOTTOM,
                ConstraintSet.PARENT_ID,
                ConstraintSet.BOTTOM,
                0)

        // app:layout_constraintLeft_toLeftOf="parent"
        constraintSet.connect(
                button.id,
                ConstraintSet.LEFT,
                ConstraintSet.PARENT_ID,
                ConstraintSet.LEFT,
                0)

        // app:layout_constraintRight_toRightOf="parent"
        constraintSet.connect(
                button.id,
                ConstraintSet.RIGHT,
                ConstraintSet.PARENT_ID,
                ConstraintSet.RIGHT,
                0)

        // app:layout_constraintTop_toTopOf="parent"
        constraintSet.connect(
                button.id,
                ConstraintSet.TOP,
                ConstraintSet.PARENT_ID,
                ConstraintSet.TOP,
                0)

        constraintSet.applyTo(cLayout)

        // ConstraintLayout set on ContentView
        setContentView(cLayout)
    }
}

これでボタンが中央に実装されました。

ボタンがクリックしたことを検知

ボタンのクリック検知にはOnClickListenerを使います
onCreateメソッドの中のbuttonの定義の後に次のコードを加えます

MainActivity.kt
        button.setOnClickListener {
            Log.v(MainActivity::class.java.simpleName, "onButtonClick")
        }

これで、ボタンを押すとログにonButtonClickと表示するアプリになりました

背景の色を変更

背景色だけはJavaとほとんど変わらず、setBackgroundColorを使えばできます
Kotlinでは三項演算子がなく、ifを式として扱えるのは特徴的な気がします

MainActivity.ktの上部
package jp.cha84rakanal.hellotogglecolor_studio_kotlin

import android.app.Activity
import android.graphics.Color
import android.os.Bundle
import android.util.Log
import androidx.constraintlayout.widget.ConstraintLayout
import androidx.constraintlayout.widget.ConstraintSet
import android.widget.Button
import android.view.View

class MainActivity : Activity() {

    private var mToggle = true

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)

        val cLayout = ConstraintLayout(this)
        cLayout.setBackgroundColor(Color.RED)
        val button = Button(this)
        cLayout.addView(button)

        val viewId = View.generateViewId()
        button.id =  viewId
        button.text = "Hello World!"

        button.setOnClickListener {
            Log.v(MainActivity::class.java.simpleName, "onButtonClick")
            mToggle = !mToggle
            cLayout.setBackgroundColor(if(mToggle) Color.RED else Color.BLUE)
        }

見た目は、Javaで作られたのと変わらなかったです...

C++

Android Studioで使える言語は、Java Kotlin だけではありません。 NDK(Native Development Kit)を利用すれば C/C++ でも開発ができます
いままでJava/Kotlinで開発していた Activity をまるごと実装することができるので、今回はNativeActivityを使って、目的のアプリをつくります。

  1. NDKを使える環境をインストールします。 Tools > SDK Managerを使って、NDK/CMakeをインストールします。(File > Project Structureからもダウンロードできます)
    File > Project Structure を開いて、Android NDK Location にNDKをインストールしたディレクトリパスが指定されていればOKです
    image.png

  2. 一からプロジェクトを作成すると大変なので、既にできているサンプルを改造します。 File > New > Import Module...を開き、検索窓にNativeと打ち込みます。するとサンプルの一覧にNdk > Native Activity とあるのでそれを選択します。
    image.png

  3. 必要な情報を入力します
    image.png

Finish を押すとサンプルがGitHubからクローンされます。

  1. 自動でプロジェクトが開くので、 File > Sync Project with Gradle Files を押します。それが終わったら兎にも角にもビルドしてアプリをRUNしましょう

output.gif

ボタンを実装

これからボタンを実装していきます。C++だけで実装されたアプリのソースは、app/src/main/cpp/main.cppだけなので、ボタンも描画もここに実装していくしかなさそうです。しかし、TeapotのNDKサンプルでは、Java側でUIが書かれているので参考にしていきます。

まずは、Java側のActivityを実装していきます。
app/src/mainjavaディレクトリを作成して、javaディレクトリにActivityを作成します。javaディレクトリを右クリックして New > Activity > Empty Activityを選択します。パッケージ名はここではjp.cha84rakanal.hellotogglecolor_studio_c_plusplusにします。

image.png

MainActivity.javaとactivity_main.xmlが自動生成されて、ディレクトリの構造は以下のようになります。

main以下の構造
main
├── AndroidManifest.xml
├── cpp
│   ├── CMakeLists.txt
│   └── main.cpp
├── java
│   └── jp
│       └── cha84rakanal
│           └── hellotogglecolor_studio_c_plusplus
│               └── MainActivity.java
└── res
    ├── layout
    │   └── activity_main.xml
    ├── mipmap-hdpi
    │   └── ic_launcher.png
    ├── mipmap-mdpi
    │   └── ic_launcher.png
    ├── mipmap-xhdpi
    │   └── ic_launcher.png
    ├── mipmap-xxhdpi
    │   └── ic_launcher.png
    └── values
        └── strings.xml

自動生成されてMainActivity.javaはAppCompatActivityクラスを継承していますが、C++のActivityと共存させるためにNativeActivityクラスを継承します。 MainActivity.javaの内容は次のようにして、アプリが起動したらログにin onCreateと出るようにします。

MainActivity.java
package jp.cha84rakanal.hellotogglecolor_studio_c_plusplus;

import android.app.NativeActivity;
import android.os.Bundle;
import android.util.Log;

public class MainActivity extends NativeActivity {

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        Log.v("java_code","in onCreate");
    }

}

このままでは、MainActivity.javaが起動しないので、AndroidManifest.xmlを編集しておきます。application要素のandroid:hasCode="false"を消して、activity要素のandroid:nameを先程作成したActivityの名前にします。

AndroidManifest.xml
<?xml version="1.0" encoding="utf-8"?>
<!-- BEGIN_INCLUDE(manifest) -->
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
          package="jp.cha84rakanal.hellotogglecolor_studio_c_plusplus"
          android:versionCode="1"
          android:versionName="1.0">

<!-- This .apk has no Java code itself, so set hasCode to false. -->
<application
    android:allowBackup="false"
    android:fullBackupContent="false"
    android:icon="@mipmap/ic_launcher"
    android:label="@string/app_name">
    <!-- Our activity is the built-in NativeActivity framework class.
        This will take care of integrating with our NDK code. -->
    <activity android:name=".MainActivity"
              android:label="@string/app_name"
              android:configChanges="orientation|keyboardHidden">
            <!-- Tell NativeActivity the name of our .so -->
            <meta-data android:name="android.app.lib_name"
                android:value="native-activity" />
            <intent-filter>
                <action android:name="android.intent.action.MAIN" />
                <category android:name="android.intent.category.LAUNCHER" />
            </intent-filter>
        </activity>
    </application>

</manifest>
<!-- END_INCLUDE(manifest) -->

これでアプリをビルド/RUNするとC++で作成したmain.cppとJavaで作成したMainActivity.javaが起動していることを確認できると思います。

次は、XMLで定義したViewを表示していけるようにします。サンプルをみると、onCreateではsetContentViewをしておらず、showUIというメソッドでUIを表示しているみたいです。なのでまずはMainActivityshowUIメソッドを実装します。

MainActivity.java
    MainActivity activity;
    PopupWindow popupWindow;

    // Our popup window, you will call it from your C/C++ code later
    @SuppressLint("InflateParams")
    public void showUI() {

        Log.v("java_code","showui Called");

        if( popupWindow != null )
            return;

        activity = this;

        this.runOnUiThread(()->{

            LayoutInflater layoutInflater
                    = (LayoutInflater)getBaseContext()
                    .getSystemService(LAYOUT_INFLATER_SERVICE);
            View popupView = layoutInflater.inflate(R.layout.activity_main, null);

            popupWindow = new PopupWindow(
                    popupView,
                    WindowManager.LayoutParams.WRAP_CONTENT,
                    WindowManager.LayoutParams.WRAP_CONTENT);

            LinearLayout mainLayout = new LinearLayout(activity);
            ViewGroup.MarginLayoutParams params = new ViewGroup.MarginLayoutParams(WindowManager.LayoutParams.WRAP_CONTENT, WindowManager.LayoutParams.WRAP_CONTENT);
            params.setMargins(0, 0, 0, 0);

            activity.setContentView(mainLayout, params);

            // Show our UI over NativeActivity window
            popupWindow.showAtLocation(mainLayout, Gravity.CENTER, 0, 0);
            popupWindow.update();

        });

    }

layoutInflater.inflateR.layout.activity_mainをしているので、activity_main.xmlを次のように変更しておきます

activity_main.xml
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout
    xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    tools:context=".MainActivity">

    <TextView
        android:id="@+id/textView"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_centerInParent="true"
        android:text="Hello World!" />

</RelativeLayout>

これで、ビルド/RUNで画面の真ん中にHello World!!と表示されるわけではありません。showUIメソッドがどこからも呼び出されていません。じゃあどこから呼び出すのかというとC++で実装しているNativeActivitymain.cppから呼び出します。 main.cppに次の関数を加えます。実行中のVMにアタッチして、showUIをC++側から呼び出せるようにします。

main.cpp
void showUI(struct engine* engine) {
    JNIEnv* jni;
    engine->app->activity->vm->AttachCurrentThread(&jni, NULL);

    // Default class retrieval
    jclass clazz = jni->GetObjectClass(engine->app->activity->clazz);
    jmethodID methodID = jni->GetMethodID(clazz, "showUI", "()V");
    jni->CallVoidMethod(engine->app->activity->clazz, methodID);

    engine->app->activity->vm->DetachCurrentThread();
    return;
}

そして、engine_init_display(struct engine* engine)の一番最後で、加えたshowUI(struct engine* engine)を呼び出します。

main.cpp
/**
 * Initialize an EGL context for the current display.
 */
static int engine_init_display(struct engine* engine) {
    // initialize OpenGL ES and EGL

    //-----省略-----//

    // Initialize GL state.
    glHint(GL_PERSPECTIVE_CORRECTION_HINT, GL_FASTEST);
    glEnable(GL_CULL_FACE);
    glShadeModel(GL_SMOOTH);
    glDisable(GL_DEPTH_TEST);

    ShowUI(engine); //<--さっき追加した関数

    return 0;
}

これで、NativeActivityでの画面の初期化が終わったあとに、Java側でViewをオーバーレイして表示します。

output2.gif

これで、背景はC++でUIがJavaの状態で画面中央にHello Worldと表示されました!
ここまで来るとボタンの設置は簡単です。activity_main.xmlを次のように変更するだけでボタンを実装できます。

activity_main.xml
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout
    xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    tools:context=".MainActivity">

    <Button
        android:id="@+id/button"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_centerInParent="true"
        android:text="Hello World!" />

</RelativeLayout>

image.png

ボタンがクリックしたことを検知

Java側でボタンがクリックされたことを検知することは簡単ですね。ボタンにbuttonとIDをふっているので、setOnClickListenerメソッドを使っておしまいです。

MainActivity.java
popupView.findViewById(R.id.button).setOnClickListener(v -> {
        Log.e("java_code","ButtonClicked Called");
});

Java側では検知できましたが、C++側は何もわかりません。背景の描画を担当しているのは、C++側なので、JavaからC++の関数を叩いて上げる必要があります。

まずmain.cppにJavaから呼ばれる関数を作成します。命名規則は Java_{Package Name}_{呼び出し元クラス名}_{メソッド名} です。引数や戻り値などついてもろもろありますがここでは説明ません。詳しいことはandroid jniとかでググってください。 main.cpp に次のような関数を追加します。

main.cpp
extern "C"
JNIEXPORT void JNICALL
Java_jp_cha84rakanal_hellotogglecolor_1studio_1c_1plusplus_MainActivity_toggle(JNIEnv * env, jobject obj){
    LOGW("called in native");
}

次に、MainActivityから呼び出せるようにします。System.loadLibraryメソッドの引数に、native-activityを指定して、Java側でC++の関数を使えるようにロードします。あとは、C++に実装した関数を、 public native {戻り値の型} {関数名}(); のようにJavaで定義します。そうすることで、Javaメソッドのように普通にC++の関数を呼び出せます。

MainActivity.java
public class MainActivity extends NativeActivity {

    MainActivity activity;
    PopupWindow popupWindow;

    static {
        System.loadLibrary("native-activity");
    }
    public native void toggle();

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        Log.v("java_code","in onCreate");
    }

    //----省略----//
            popupView.findViewById(R.id.button).setOnClickListener(v -> {
                Log.e("java_code","ButtonClicked Called");
                toggle();
            });
    //----省略----//

これで、ボタンをを押すと、Java側で、ButtonClicked Calledとログが出力され、C++でcalled in native とログが出力されます。

image.png

これで、C++でもボタンがクリックされたことわかりました。

背景の色を変更

背景の描画はmain.cppengine_draw_frame(struct engine* engine)で行われています。実装をみると、EGL/OpenGLESを使って描画されているのがわかります。とすると、単に背景を塗りつぶすだけならglClearColorで良さそうです。なのでmain.cppを次のように変更していきます。

main.cpp
static int toggle_count = 0;

extern "C"
JNIEXPORT void JNICALL
Java_jp_cha84rakanal_hellotogglecolor_1studio_1c_1plusplus_MainActivity_toggle(JNIEnv * env, jobject obj){
    LOGW("called in native");
    toggle_count++;
}

/**
 * Just the current frame in the display.
 */
static void engine_draw_frame(struct engine* engine) {
    if (engine->display == NULL) {
        // No display.
        return;
    }

    if(toggle_count%2 == 0){
        // Just fill the screen with a color.
        glClearColor(1,0,0,1);
    }else{
        glClearColor(0,0,1,1);
    }


    glClear(GL_COLOR_BUFFER_BIT);

    eglSwapBuffers(engine->display, engine->surface);
}

engine.animatingが1じゃないとengine_draw_frame(struct engine* engine)が呼ばれないので注意

output3.gif

とりあえずこれでC++??でも同じアプリができました!
Nativeで動いてるとなんだかよくわからんけど速いって気持ちになっていいですね。Android NDKも色々できることがあるので今後まとめて行けたらと思います。

Unity

image.png

  • ゲーム開発エンジン
  • クロスプラットフォームの開発が可能
  • アセットと呼ばれる素材やツールが便利
  • プラグインという形でOS固有のAPIを呼び出すことも可能

それでは、Unityを使ってAndroidアプリの作成をおこなっていきます!

  1. Unity Editor/Unity Hubのインストール(説明は割愛します。 Android Build Support を入れるのを忘れずに)
  2. Unity Hubが便利なので、Unity Hubを開いて新規作成を押します。
    image.png

  3. 今回は3Dゲームとかを作るわけでは無いので、2Dを選択して、必要な情報を入力します。
    image.png

これでUnity Editorの画面が表示されます。

image.png

兎にも角にもこの状態でAndroid上で動かして、エラーとか起こらないことを確認しましょう。
File > Build Settingsを開くと、 PC Mac & Linux Standalone にUnityマークがあるので、Androidを選択してSwitch Platform を押します。

image.png

そうすると Android にUnityマークがついて、Build and Runを押せるので押します。ファイルダイアログが出るので、適当にBuild ディレクトリを作ってSave を押します。そうすると、Unity内部でビルドがはしってAPKが生成され自動でインストールされ実行されます。
image.png

Unityのロゴが表示されて、何もない暗いブルーが表示されます。

output4.gif

ボタンを実装

それでは、ボタンを実装していきます。Unityではシーンにコンポーネントを設置して、ゲームやアプリを作っていきます。
Hierarchyウィンドウで右クリックを押してUI > Buttonをクリックでボタンが設置されます。
image.png

これでBuild and Runをするとなんとなく中央から左下に向かう途中あたりにボタンが来ます。
image.png

位置を真ん中にして押しやすいようにもうちょっと大きくしようと思います。
Hierarchyに表示されているCanvasをクリックすると右側のInspectorに、Canvasに設定されてるコンポーネントの情報が表示されます。
Canvas Scaler (Script)という項目があるので、そこの設定を以下のようにしましょう。これで、横幅を基準に横1080x縦1920の領域として画面がレンダリングされます。
image.png

Unity EditorのGameウィンドウで確認すると、アスペクト比や位置を維持していますね。
output5.gif

次は、Hierarchyに表示されているCanvas > Buttonをクリックすると右側のInspectorに、Buttonに設定されてるコンポーネントの情報が表示されます。
Rect Transformという項目があるので、そこの設定を以下のようにしましょう。これでボタンが中心に位置して大きくなります。
image.png

文字もよしなに大きくするとAndroid上でこんな感じになります。
image.png

ボタンの実装はこれで良さそうです。

ボタンがクリックしたことを検知

次は、ボタンイベントを取得しましょう。Assets > Create > C# ScriptでC#ファイルをUntiyプロジェクトに作成します。名前は今回はButtonClickとしました。 Start() メソッドはUnityのロジックから呼ばれるものでシーンの1番最初に呼ばれます。Update()メソッドもUnityのロジックから呼ばれるものでシーンの描画の際毎回呼ばれます。このクラスにボタンが押されたら呼ばれるメソッドを実装します。メソッド名は適当に、onClickにして、呼ばれたらログを出すようにしておきます。

ButtonClick.cs
using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class ButtonClick : MonoBehaviour
{
    // Start is called before the first frame update
    void Start()
    {

    }

    // Update is called once per frame
    void Update()
    {

    }

    public void onClick(){
        Debug.Log("On Click");
    }
}

この作成したButtonClickクラスをインスタンス化できるように、GameObjectにアタッチしようと思います。
Hierarchyウィンドウで右クリックを押してCreate EmptyをクリックでGameObjectが設置されます。この設置されたGameObjectをクリックすると、右側のInspectorにはTransformだけが表示されていると思います。GameObjectButtonClickクラスをアタッチするのは簡単で、先ほど作成したC#ScriptをInspectorウィンドウにドラッグアンドドロップするだけです。(Add Componentからもアタッチできます。

image.png

GameObjectへのアタッチを終えたら、次はButtonからonClick()呼べるようにします。HierarchyウィンドウにあるButtonのInspectorを開きます。Button (Script)の項目にOn Click()があり、そこは、List is Emptyになってると思います。
image.png

On Click()についている+マークを押すと、このようにListに要素が追加されます。
image.png

Hierarchyウィンドウにある先ほど作成したGameObjectOn Click()のListにあるNone (Object)に向かってドラッグアンドドロップすると、次のようになり、ButtonがButtonClickクラスのメソッドにアクセスできる状態になります
image.png

No Functionとなっているところをクリックすると、リストがドロップダウンするので、先ほどGameObjectにアタッチしたクラスと、呼びたいメソッドを指定できます。
image.png

これでボタンを押すと、ログとOn Click表示されます。実際のUnity Editor上で確認するとこんな感じです。
output6.gif

背景の色を変更

あとは、背景を色で塗りつぶすだけです。Hierarchyウィンドウで右クリックを押してUI > Panelをクリックで、画面を覆う1枚のパネルが設置されます。UnityのHierarchyは上の方が奥側なので、PanelButtonより上にします。設置したPanelInspectorを開くと、Image (Script)Colorの項目があるので、これをいじるとパネルの色が変わります。
output7.gif

このパネルの色をonClick()メソッド内で変えることができると良さそうですね。色を変えられるようにButtonClick.csを次のように変更します。このスクリプトのこの状態では、ImageクラスのimgNullなような気がします。実際、このままではNullなので、Unity Editor上でImageクラスのimgにオブジェクトを代入?アタッチ?します。

ButtonClick.cs
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.UI;

public class ButtonClick : MonoBehaviour
{

    public Image img;
    private bool toggle = false;

    // Start is called before the first frame update
    void Start()
    {

    }

    // Update is called once per frame
    void Update()
    {

    }

    public void onClick(){
        Debug.Log("On Click");
        img.color = (toggle)? Color.red : Color.blue;
        toggle = !toggle;
    }
}

GameObjectInspectorを開いてみると、ButtonClick (Script)imgの項目がNone (Image)になっています。None (Image)の右側のターゲットマークをクリックするとImageをアタッチしているGameObjectの一覧が表示されるので、Pnaelを選択します。するとimgにPanelにアタッチされたImageが代入?アタッチ?されます。
output8.gif

これで背景色を変えられるようになったので、アプリは完成です!
output9.gif

(4つも同じアプリを作ってかつまとめると疲れてきますね...

Processing

image.png

  • クリエイティブコーディングのためのツール
  • エンジニアではない人でも簡単に扱える
  • ライブラリが様々あって便利
  • 言語はJavaで描画をうまくWrapしてくれている

傾向を変えてアAndroidアプリ開発としてはマイナーなProcessingでアプリを作って行きます。

  1. Processingをインストールします。公式のダウンロードページからダウンロードしてZIPを解凍するだけです。
  2. Processingを起動すると、IDEが立ち上がってすぐにコーディングを開始できます。デフォルトではJavaモードなので、PC上でプログラムが動作します。 image.png
  3. IDE右上のJava ▼とあるところをクリックするとモードの追加...とあるのでクリックします。すると、ライブラリマネージャーが表示されるので、Android Modeを選択してInstallを押します。インストールが完了するとstatusに緑のチェックマークがつくので、ライブラリマネージャーを閉じます。
    image.png

  4. 閉じた後、IDE右上のJava ▼とあるところをクリックするとAndroidの項目が追加されているので、Androidを押すとAndroidモードになります。

  5. またまた、兎にも角にも実行してみます。 Android > Devices >にPCに接続しているデバイスがあることを確認したら、IDE左上の▶マークを押します。すると、ビルドが走りインストールされアプリがRUNします。
    image.png

image.png

ボタンを実装

Processingは描画が専門な感じなので、UIを作るとなるとライブラリが必要になります。 ProcessingではcontrolP5 という、GUI作成するライブラリがあるので、これを使います。

スケッチ > インポートライブラリ > ライブラリを追加... でライブラリマネージャーが開くので、ControlP5を検索し、インストールします。Processingといっても基本はJavaなので、ライブラリのインポートは import controlP5.*; でおしまいです。ボタンをただ置くだけならIDEに次のプログラムを書くだけです。

HelloToggleColor.pde
import controlP5.*;

ControlP5 control;

void setup() {
  fullScreen();

  control = new ControlP5(this);
  control.addButton("onClick")
         .setLabel("Red_Button")
         .setPosition(50, 40)
         .setSize(100, 40)
         .setColorActive(color(0, 40))    //押したときの色
         .setColorBackground(color(255))  //通常時の色
         .setColorForeground(color(255))  //マウスを乗せた時の色
         .setColorCaptionLabel(color(0)); //テキストの色

}

void draw() {

}

image.png

めちゃくちゃ小さいですが、ボタンを置くことができました。

ボタンがクリックしたことを検知

クリックの検知は簡単で、addButton()の引数と同じ名前のメソッドを作成するだけで、ボタンイベントを拾うことができます。今回は"onClick"としているのでonClick()メソッドを実装します。printlnメソッドを使うとProcessingのIDEのコンソールに文字を表示させられます。

HelloToggleColor.pde
import controlP5.*;

ControlP5 control;
int count = 0;

void setup() {
  fullScreen();

  control = new ControlP5(this);
  control.addButton("onClick")
         .setLabel("Red_Button")
         .setPosition(50, 40)
         .setSize(100, 40)
         .setColorActive(color(0, 40))    //押したときの色
         .setColorBackground(color(255))  //通常時の色
         .setColorForeground(color(255))  //マウスを乗せた時の色
         .setColorCaptionLabel(color(0)); //テキストの色

}

void draw() {

}

void onClick(){
  println("onClick:" + count);
  count++;
}

output10.gif

クリックの検知もできたので、文字もボタンも小さいのを改善します。
まずは文字を改善します。、ツール > フォントの作成...を開いて、好きなフォントを選びます。今回はHelveticaを選びました。サイズを128ぐらいにして、OKを押すとフォントファイルが生成されます。

image.png

フォントファイルはloadFontメソッドで読み込んで使います。ボタンのサイズはsetPosition(int,int) setSize(int,int)を使うと改善できそうです。なのでHelloToggleColor.pdeを次のように変更します。

HelloToggleColor.pde
import controlP5.*;

ControlP5 control;
int count = 0;

void setup() {
  fullScreen();

  PFont p = loadFont("Helvetica-128.vlw"); 
  ControlFont font = new ControlFont(p);

  font.setSize(32);

  int button_width = (int)(width*0.25);
  int button_height = (int)(height*0.08);

  control = new ControlP5(this);
  control.setFont(font);
  control.addButton("onClick")
         .setLabel("Button")
         .setSize(button_width,button_height)
         .setPosition(width/2 - button_width/2, height/2 - button_height/2)
         .setColorActive(color(0, 40))    //押したときの色
         .setColorBackground(color(255))  //通常時の色
         .setColorForeground(color(255))  //マウスを乗せた時の色
         .setColorCaptionLabel(color(0)); //テキストの色

}

void draw() {

}

void onClick(){
  println("onClick:" + count);
  count++;
}

image.png

文字のサイズ、ボタンの位置、ボタンのサイズがちょうどよくなりました。

背景の色を変更

Processingで背景を特定の色で塗りつぶすのは簡単です。backgroundメソッドを使うだけなので、プログラムは次のようになります。

HelloToggleColor.pde
import controlP5.*;

ControlP5 control;
int count = 0;
boolean toggle = false;

void setup() {
  fullScreen();
  background(255, 0, 0);

  PFont p = loadFont("Helvetica-128.vlw"); 
  ControlFont font = new ControlFont(p);

  font.setSize(32);

  int button_width = (int)(width*0.25);
  int button_height = (int)(height*0.08);

  control = new ControlP5(this);
  control.setFont(font);
  control.addButton("onClick")
         .setLabel("Button")
         .setSize(button_width,button_height)
         .setPosition(width/2 - button_width/2, height/2 - button_height/2)
         .setColorActive(color(0, 40))    //押したときの色
         .setColorBackground(color(255))  //通常時の色
         .setColorForeground(color(255))  //マウスを乗せた時の色
         .setColorCaptionLabel(color(0)); //テキストの色

}

void draw() {

}

void onClick(){
  println("onClick:" + count);
  count++;
  toggle = !toggle;
  if(toggle){
    background(0, 0,255);
  }else{
    background(255, 0, 0);
  }
}

output11.gif

これでProcessingでも完成しました。

openFrameworks

image.png

  • クリエイティブコーディングのためのツール
  • エンジニアではない人でも簡単に扱える
  • アドオンが豊富で便利
  • C++で書かれているため速度だせる(?)

同じく、Androidアプリ開発としてはマイナーなopenFrameworksでアプリを作ります。
この記事では v0.11.0android 版を使います。

  1. openFrameworksをインストールします。これも、公式のダウンロードページからダウンロードしてZIPを解凍するだけです。 今回は、mobileの行にあるAndroid版をダウンロードしてきます。

image.png

2.of_v0.11.0_android_release/projectGenerator-{OS} ディレクトリにあるprojectGeneratorを開きます。初めて開くとおそらくopenFrameworksのパスを求められるので先ほどダウンロードして展開したディレクトリを選択します。Project Nameにプロジェクトの名前、Project pathにプロジェクトの保存先を指定して、Generateを押すとopenFrameworksのプロジェクトが生成されます。(念のためPlatformがAndroidになっていることを確認しておきましょう
image.png
image.png
Android Studio側でTools -> Create Command-line Launcher...の設定を行っているとOpen in IDEでAndroid Studioが開きます。

3.筆者は設定していないので手動でAndroid Studioを開き、 Import Project (Gradle ,Eclipse ADT, etc.) をクリックして、生成されたプロジェクトのディレクトリを選択します。Openを押すと自動でGradle Syncが走ります。(Gradle Syncが走らない場合は、手動でSyncします。
alt

4.Gradle Syncに失敗した場合は、build.gradlesettings.gradleを修正する必要があります。スクリプトファイルでは、openFrameworksのパスが../../../になっているので正しいパスに直します。
自分の環境では/Users/cha84rakanal/Documents/of_v0.11.0_android_releaseディレクトリにopenFrameworksを配置しているので、そのパスに置き換えます。

build.gradle上部
def ofRoot(){ return '/Users/cha84rakanal/Documents/of_v0.11.0_android_release/' }

// Load common functions
apply from: ofRoot()+"libs/openFrameworksCompiled/project/android/common-functions.gradle"

buildscript {
    apply from: "/Users/cha84rakanal/Documents/of_v0.11.0_android_release/libs/openFrameworksCompiled/project/android/ndk-verify.gradle"

    repositories {
        jcenter()
    }
    dependencies {
        // Using the gradle-experimental version that supports c++
        classpath 'com.android.tools.build:gradle-experimental:0.9.3'
    }
}
settings.gradle上部
// openFrameworks-relative root directories (don't touch)

def ofRoot = '/Users/cha84rakanal/Documents/of_v0.11.0_android_release/'

// Load common functions
apply from: ofRoot+"libs/openFrameworksCompiled/project/android/common-functions.gradle"

ERROR: Wrong version of NDK library found. Found version 19.2.5345600, but openFrameworks requires version r15cというエラーが出た場合は、local.propertiesを編集してAndroid NDK r15cのインストールされてるパスを指定します。

5.Gradle Syncが通れば、まずは実行してみます
image.png
灰色で背景が塗りつぶされていれば正しいです。

ボタンを実装

openFrameworksもProcessingと同様に、描画が専門な感じなので、UIを作るとなるとアドオンが必要になります。openFrameworksではofxDatGui という、GUI作成するアドオンがあるので、これを使います。 試してみたらofxDatGuiを使ってできなかったので、 Android Studio + NDKで使った方法でボタンを実装していきます。(この方法はずるいなぁと思いながら...

AndroidManifest.xmlをみると、起動するActivityがcc.openframeworks.HelloColorToggle.OFActivityとなっているので、srcJava/OFActivity.javaを見てみます。

AndroidManifest.xml
       <activity
            android:name="cc.openframeworks.HelloColorToggle.OFActivity"
            android:label="@string/app_name"
            android:configChanges="orientation|screenSize|screenLayout|smallestScreenSize">
            <intent-filter>
                <action android:name="android.intent.action.MAIN" />
                <category android:name="android.intent.category.LAUNCHER" />
            </intent-filter>
        </activity>

OFActivity.javaにはこれといった実装はないのですが、cc.openframeworks.OFActivityクラスを継承しています。なので、cc.openframeworks.OFActivityにマウスカーソルを合わせて、右クリックしてGo To > Declarationで定義に飛びます。
すると、this.setContentView(view);で、R.layoutのXMLで定義されたViewを読み込んでいることを発見できました。

cc.openframeworks.OFActivity.java
public void initView(){  
        String packageName = this.getPackageName();
        try {
            Log.v("OF","trying to find class: "+packageName+".R$layout");
            Class<?> layout = Class.forName(packageName+".R$layout");
            View view = this.getLayoutInflater().inflate(layout.getField("main_layout").getInt(null),null);
            if(view == null) {
                Log.w("OF", "Could not find main_layout.xml.");
                throw new Exception();
            }
            this.setContentView(view);

            Class<?> id = Class.forName(packageName+".R$id");
            mOFGlSurfaceContainer = (ViewGroup)this.findViewById(id.getField("of_gl_surface_container").getInt(null));

            if(mOFGlSurfaceContainer == null) {
                Log.w("OF", "Could not find of_gl_surface_container in main_layout.xml. Copy main_layout.xml from latest empty example to fix this warning.");
                throw new Exception();
            }

        } catch (Exception e) {
            Log.w("OF", "couldn't create view from layout falling back to GL only",e);
            mOFGlSurfaceContainer = new FrameLayout(this);
            this.setContentView(mOFGlSurfaceContainer);
        }
    }

なので、ボタンを配置するだけなら、res/layout/main_layout.xmlで配置するだけでおしまいです。main_layout.xmlを次のように編集するとボタンが置かれます

main_layout.xml
<?xml version="1.0" encoding="utf-8"?>
   <RelativeLayout android:id="@+id/relativeLayout1" android:layout_width="fill_parent" android:layout_height="fill_parent" xmlns:android="http://schemas.android.com/apk/res/android">
       <RelativeLayout android:id="@+id/of_gl_surface_container" android:layout_width="fill_parent" android:layout_height="fill_parent"/>
       <Button
           android:id="@+id/button"
           android:layout_width="wrap_content"
           android:layout_height="wrap_content"
           android:text="Button" />
       <!-- add here other views' layouts -->
    </RelativeLayout>

image.png

ボタンがクリックしたことを検知

次は、ボタンがクリックしたことを検知します。親クラスでsetContentView()したViewに子クラスでもアクセスできるのでsrcJava/OFActivity.javaでボタンにOnClickListenerを実装します。(普通に親のabstructクラスに実装はするのはよくないし...
サクッとfindViewById()メソッドをつかってsetOnClickListener()メソッドでOnClickListener当てます。これで、ボタンをクリックするとログにonClickと表示されます。

OFActivity.java
public class OFActivity extends cc.openframeworks.OFActivity{

    @Override
    public void onCreate(Bundle savedInstanceState)
    {
        super.onCreate(savedInstanceState);
        findViewById(R.id.button).setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View view) {
                Log.e("of","onClick");
            }
        });
    }

今回も描画自体は、C++側で実装されているので、JNIを使ってC++側にボタンがクリックされたことを伝えて行きます。
まずは、C++側にJavaから呼び出せる関数を実装します。src/ofApp.h#include <jni.h>を加えます。

ofApp.h
#pragma once

#include "ofMain.h"
#include "ofxAndroid.h" 
#include <jni.h> // <---- 追加

class ofApp : public ofxAndroidApp{

    public:
//----省略----//

次に、実際に呼び出す関数をsrc/ofApp.cppに実装します。今回はclickというメソッド名で呼び出せるようにします。命名規則通りの関数名は次のようになります。

ofApp.h
#include "ofApp.h"

extern "C"
JNIEXPORT void JNICALL
Java_cc_openframeworks_HelloColorToggle_OFActivity_click(JNIEnv* env,jobject thiz){
    ofBackground(255,255,255);
}

//--------------------------------------------------------------
void ofApp::setup(){

}

あんまり良くないTipsですが、 C++でつける関数名がわからない場合は、C++で何も実装せずにJava側public native {戻り値の型} {関数名}(引数...);とメソッド名だけ定義して適当なところで呼び出す形で実行します。アプリがUnsatisfiedLinkErrorをだして、本来呼べたはずの関数名をログに出力するので、それを使います。下の場合だとhoge()というメソッド名で関数を作成したい場合はJava_cc_openframeworks_HelloColorToggle_OFActivity_hogeという関数名でC++側で実装するべきたとわかります。

UnsatisfiedLinkError
2019-12-03 12:18:09.617 17648-17648/cc.openframeworks.HelloColorToggle E/AndroidRuntime: FATAL EXCEPTION: main
    Process: cc.openframeworks.HelloColorToggle, PID: 17648
    java.lang.UnsatisfiedLinkError: No implementation found for void cc.openframeworks.HelloColorToggle.OFActivity.hoge() (tried Java_cc_openframeworks_HelloColorToggle_OFActivity_hoge and Java_cc_openframeworks_HelloColorToggle_OFActivity_hoge__)
        at cc.openframeworks.HelloColorToggle.OFActivity.hoge(Native Method)
        at cc.openframeworks.HelloColorToggle.OFActivity$1.onClick(OFActivity.java:27)
        at android.view.View.performClick(View.java:6294)
        at android.view.View$PerformClick.run(View.java:24770)
        at android.os.Handler.handleCallback(Handler.java:790)
        at android.os.Handler.dispatchMessage(Handler.java:99)
        at android.os.Looper.loop(Looper.java:164)
        at android.app.ActivityThread.main(ActivityThread.java:6494)
        at java.lang.reflect.Method.invoke(Native Method)
        at com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run(RuntimeInit.java:438)
        at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:807)

OFActivity.javaを次のように編集すると、ボタンをクリックすると背景が白く塗りつぶされるアプリが完成します。

OFActivity.java
public class OFActivity extends cc.openframeworks.OFActivity{

    public native void click();

    @Override
    public void onCreate(Bundle savedInstanceState)
    {
        super.onCreate(savedInstanceState);
        findViewById(R.id.button).setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View view) {
                click();
            }
        });
    }

output12.gif

背景の色を変更

ボタンのクリックでもうやってしまいましたが、背景色を変えるのは、ofBackground(int 0-255,int 0-255,int 0-255);ですね。openFrameworksの構造はofApp::setup()はアプリが立ち上がった後の1回のみ呼ばれて、画面描画ごとにofApp::update() ofApp::draw() が呼ばれます。それを踏まえて、ofApp.cppを次のように編集します。

ofApp.cpp
#include "ofApp.h"

static bool toggle;

extern "C"
JNIEXPORT void JNICALL
Java_cc_openframeworks_HelloColorToggle_OFActivity_click(JNIEnv* env,jobject thiz){
    toggle = !toggle;
}

//--------------------------------------------------------------
void ofApp::setup(){
    toggle = true;
    ofBackground(255,0,0);
}

//--------------------------------------------------------------
void ofApp::update(){

}

//--------------------------------------------------------------
void ofApp::draw(){
    if(toggle)
        ofBackground(255,0,0);
    else
        ofBackground(0,0,255);
}

ボタンはmain_layout.xmlのボタン要素にandroid:layout_centerInParent="true"を加えるだけで真ん中にきます。
output13.gif

Cordova

image.png

  • 簡単にクロスプラットフォーム
  • HTML/JS/CSSが分かれば、アプリを作れる
  • Webの資産を使い回せる

今度は、Nativeから離れて、WebViewを元に作られているCordovaというフレームワークを使ってアプリを作ります。

1.Cordovaをインストールします。npmが入っていることが前提です。ターミナルに次のコマンドを打ち込むだけでインストールは終わりです。

cardovaのインストール
$npm install -g cordova

2.アプリのプロジェクトを作成します。事前にアプリのプロジェクトを生成するディレクトリへ移動しておきます。

HelloToggleColorというアプリ名でcordovaのプロジェクトを作成
$cordova create HelloToggleColor

プロジェクトディレクトリを見てみると、index.htmlindex.js が見受けられるので、どうやらこれらを編集して開発をすすめていけば良さそうな気がします。

プロジェクトのディレクトリ構成
HelloToggleColor
├── config.xml
├── hooks
│   └── README.md
├── package.json
├── platforms
├── plugins
└── www
    ├── css
    │   └── index.css
    ├── img
    │   └── logo.png
    ├── index.html
    └── js
        └── index.js

3.プロジェクトディレクトリに移動して、Platformを追加します。今回は、Androidアプリなのでandroidを追加します。

androidをプラットフォームとして追加
$cd HelloToggleColor
$cordova platform add android

4.アプリを起動します。実機をつないでいれば自動でインストールされた立ち上がります。

アプリのビルドと実行
$cordova run android

image.png

え、やば、簡単すぎんか...?

$adb shell dumpsys activity topを使うとViewの構成とかが見れます。なので、Cordovaアプリがどうなっているかというと、SystemWebView で構成されていることがわかります。

dumpsysでViewHierarchyの確認
TASK io.cordova.hellocordova id=140 userId=0
  ACTIVITY io.cordova.hellocordova/.MainActivity 17398e4 pid=24311      
    View Hierarchy:
      DecorView@8e127c8[MainActivity]
        android.widget.LinearLayout{ead1061 V.E...... ........ 0,0-1080,1794}
          android.view.ViewStub{6b3b786 G.E...... ......I. 0,0-0,0 #1020187 android:id/action_mode_bar_stub}
          android.widget.FrameLayout{4142847 V.E...... ........ 0,63-1080,1794 #1020002 android:id/content}
            org.apache.cordova.engine.SystemWebView{ec4f874 VFEDH.C.. .F...... 0,0-1080,1731 #64}
        android.view.View{b49519d V.ED..... ........ 0,1794-1080,1920 #1020030 android:id/navigationBarBackground}
        android.view.View{183c612 V.ED..... ........ 0,0-1080,63 #102002f android:id/statusBarBackground}

ここからの気持ちはAndroidアプリ開発ではなくモバイルWeb開発なのか...と思いながら目的のアプリを作っていきます。

ボタンを実装

HTMLなら <button>タグを追加するだけでbuttonを置けます...

index.js
<body>
   <button type=button>Button</button>
   <script type="text/javascript" src="cordova.js"></script>
   <script type="text/javascript" src="js/index.js"></script>
</body>

image.png

Androidなのに味気が無いので、マテリアルデザインっぽいボタンしていきます。
今はサポートされて無いですが、ボタンだけなら扱いやすいので、Material Design Liteを導入します。プロジェクトのディレクトリに移動して次のコマンドを実行してローカルに落としてきます。

MaterialDesignLiteのダウンロード
$npm install material-design-lite --save

CordovaのHTMLからは、基本的には、path/to/project/wwwのフォルダしかアクセスできないようなので、./node_modules/から ./wwwへcssとjsファイルをコピーします。

cp ./node_modules/material-design-lite/material.min.css ./www/css/
cp ./node_modules/material-design-lite/material.min.js ./www/js/

あとは、ヘッダーに次のコードを追加して、HTMLでの読み込みは完了です。

index.html
<link rel="stylesheet" href="./css/material.min.css">
<script src="./js/material.min.js"></script>

ボタンを中央に置きたいので index.css に次のコードを追加して、index.htmlのButton要素を囲います。

index.css
.wrap2 {
    width: 100%;
    height: 100%;
    display: flex;
    justify-content: center;
    align-items: center;
}
index.html
<div class="wrap2">
    <button class="mdl-button mdl-js-button mdl-button--raised mdl-js-ripple-effect">
        Button
    </button>
</div>

image.png

NativeのUIとほとんど変わらないように見えます

ボタンがクリックしたことを検知

これも楽ですね。ButtonタグにonClickプロパティをつけるか、JavascriptでaddEventListenerを使うかです。
今回はJavascriptを書きます。js/index.jsを開いてコードをサクッと実装します。ここでのconsole.log()は、Google ChromeのInspectorにログを出力できます。GoogleChromeのアドレスバーにchrome://inspect/と入力してページを開き、端末のinspectを押すと、実行中の画面とともに、consoleが表示されます。

index.js
var app = {
    // Application Constructor
    initialize: function() {
        document.addEventListener('deviceready', this.onDeviceReady.bind(this), false);

        document.getElementById('button1').addEventListener('click',()=>{
            console.log('click');
        });
    },

image.png

背景の色を変更

あとは背景の色を変えるだけです。CSSのbackgtoundプロパティをいじっていきます。js/index.jsを開いてコードを加えます。

index.js
var app = {

    toggle: false,

    // Application Constructor
    initialize: function() {
        document.addEventListener('deviceready', this.onDeviceReady.bind(this), false);

        document.getElementById('back').style.background = 'red';
        document.getElementById('button1').addEventListener('click',()=>{

            document.getElementById('back').style.background = (this.toggle)? 'red' : 'blue';
            this.toggle = !this.toggle;
            console.log('click');

        });
    },

ボタンに設定されているアルファ値が低いので後ろの色が反映されてしまいましたがCordovaでも同じアプリができました。

output14.gif

React Native

image.png

Getting Startedのページに行くと、React Nativeの開発ツールとしてExpo CLIを使うのとReact Native CLIを使うのとがある。
まずは、Expo CLI から試してみることにする。

1.まず、Expo CLIをインストール。

ExpoCLIのインストール
$npm install -g expo-cli

2.プロジェクトを作るディレクトリに移動して、プロジェクトを生成

プロジェクトを作成
$expo init HelloToggleColor

Node.jsのバージョンはサポートしているものに上げる必要がある。

Node.js10.8.0には対応していない....
ERROR: Node.js version 10.8.0 is no longer supported.

expo-cli supports following Node.js versions:
* >=8.9.0 <9.0.0 (Maintenance LTS)
* >=10.13.0 <11.0.0 (Active LTS)
* >=12.0.0 (Current Release)

3.どのテンプレートで作成するか聞かれるので、blankを選びます。

テンプレートの選択
? Choose a template: (Use arrow keys)
  ----- Managed workflow -----
❯ blank                 a minimal app as clean as an empty canvas 
  blank (TypeScript)    same as blank but with TypeScript configuration 
  tabs                  several example screens and tabs using react-navigation 
  ----- Bare workflow -----
  minimal               bare and minimal, just the essentials to get you started 
  minimal (TypeScript)  same as minimal but with TypeScript configuration 

4.プロジェクトが生成されたら、まずは端末で実行します。プロジェクトのディレクトリに移動して、npm start します。

アプリの起動
cd HelloToggleColor
npm start

すると、サーバーが立ち上がり、ブラウザに開発ツール、ターミナルにQRコードが表示されます。
image.png

image.png

5.実行する端末で Expo というアプリをインストールしておきます。
image.png

6.インストールされたExpoアプリでQRコードをScanすると、JavaScriptをバンドルして、アプリが表示されます。
image.png

ターミナルでは次のコマンドが使えるので、a を押すとターミナルからアプリを起動できます。

 › Press a to run on Android device/emulator, or i to run on iOS simulator, or w to run on web.
 › Press c to show info on connecting new devices.
 › Press d to open DevTools in the default web browser.
 › Press shift-d to disable automatically opening DevTools at startup.
 › Press e to send an app link with email.
 › Press p to toggle production mode. (current mode: development)
 › Press r to restart bundler, or shift-r to restart and clear cache.
 › Press s to sign in.

ボタンを実装

それでは、ボタンを実装していきます。アプリのメインが画面を作成しているところは App.jsなので、これを編集していきます。
HTMLのノリで <button> ダグを置いてみます。

App.js
import React from 'react';
import { StyleSheet, Text, View } from 'react-native';

export default function App() {
  return (
    <View style={styles.container}>
      <Text>Open up App.js to start working on your app!</Text>
      <button><Text>Button</Text></button>
    </View>
  );
}

const styles = StyleSheet.create({
  container: {
    flex: 1,
    backgroundColor: '#fff',
    alignItems: 'center',
    justifyContent: 'center',
  },
});

image.png
だめでした。

よくわからないときは公式ドキュメントを見てみます。公式ドキュメントのボタンの項目のサンプルコードを参考に書き直します。

App.js
import React from 'react';
import { StyleSheet, Text, Button,View } from 'react-native';

export default function App() {
  return (
    <View style={styles.container}>
      <Button
          onPress={this.onPressLearnMore}
          title="Learn More"
          color="#858585"
          accessibilityLabel="Learn more about this purple button"
      />
    </View>
  );
}

const styles = StyleSheet.create({
  container: {
    flex: 1,
    backgroundColor: '#fff',
    alignItems: 'center',
    justifyContent: 'center',
  },
});

image.png

なんとかボタンが表示されました。

ボタンがクリックしたことを検知

ボタンが押されたらonPressにセットしたメソッドが呼ばれるのでメソッドを実装します。

App.js
import React from 'react';
import { StyleSheet, Text, Button, View, Alert} from 'react-native';

export default function App() {

  onPressLearnMore = ()=>{
    Alert.alert('pressed button')
  };

  return (
    <View style={styles.container}>
      <Button
          onPress={this.onPressLearnMore}
          title="Learn More"
          color="#858585"
          accessibilityLabel="Learn more about this purple button"
      />
    </View>
  );
}

const styles = StyleSheet.create({
  container: {
    flex: 1,
    backgroundColor: '#fff',
    alignItems: 'center',
    justifyContent: 'center',
  },
});

ボタンを押したらダイアログが表示されるようにしました。

output15.gif

背景の色を変更

背景の色を変えます。画面いっぱいに構成されているViewコンポーネントのスタイルを変更するのが良さそうです。まずは、styleに背景が赤になるスタイルと青になるスタイルを加えます。

App.js
const styles = StyleSheet.create({
  container: {
    flex: 1,
    backgroundColor: '#fff',
    alignItems: 'center',
    justifyContent: 'center',
  },
  red: {
    flex: 1,
    backgroundColor: '#f00',
    alignItems: 'center',
    justifyContent: 'center',
  },
  blue: {
    flex: 1,
    backgroundColor: '#00f',
    alignItems: 'center',
    justifyContent: 'center',
  },
});

ボタンが押されたら、setStateを呼び出して、Viewに割り当てるスタイルを変えます。さっきは、export default fuctionでしたが、export default classに変えておきます。

App.js
export default class App extends React.Component{

  constructor(props) {
    super(props);
    this.state = {
      styleVal : styles.container,
      toggle : true
    }
  }

  onPressLearnMore = () => {
    this.setState(
      {
        styleVal : (this.state.toggle)?styles.red : styles.blue,
        toggle : !this.state.toggle
      }
    );
  }

  render = () => {
    return (
      <View style={this.state.styleVal}>
        <Button
            onPress={this.onPressLearnMore}
            title="Learn More"
            color="#858585"
            accessibilityLabel="Learn more about this purple button"
        />
      </View>
    );
  }

};

output16.gif

これでなんとかReact Nativeでもできました!

Visual Studio

image.png

まずはインストールから初めていきます。(Windowsの方はすみません

1.MSのVisual Studioのページにいき、インストーラーをダウンロードします。今回は、Visual Studio for Macをダウンロードしてきます
image.png
2.ダウンロードしたdmgを開いてインストーラーを起動します。インストールするコンポーネントのAndroidにチェックがついていることを確認してインストールを押します。
image.png
3.Visual Studioを開き、+新規でプロジェクトを新しくプロジェクトを作成します
image.png

Visual Studioで作成できるAndroidアプリはXamarin.FormsXamarin.Androidがあるので、まずはXamarin.Formsでアプリを作っていきます。

Xamarin.Forms

1.マルチプラットフォームで、空白フォームのアプリを選択します。
image.png
2.必要な情報を入力します。
image.png
3.プロジェクトの保存先などを指定して作成します。
image.png

これで、プロジェクトが作成されました。
image.png

とりあえず実機で実行してみます。この部分で実行するOSと端末を設定します。
image.png

こんな感じで Welcome to Xamarin.Forms!と画面中央に設置されます。
image.png

ボタンを実装

まずは、ボタンを設置していきます。最初の画面はMainPage.xmalで構成されているので、編集していきます。
Android Studioで読み書きするactivity_main.xmlに似ていますね。要素だったところを要素に変えるだけです。IDEの右側にコンポーネントのリストがあるので実際ドラッグアンドドロップで要素を置けるの楽ですね。

MainPage.xmal
<?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:d="http://xamarin.com/schemas/2014/forms/design" xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" mc:Ignorable="d" x:Class="HelloToggleColor.MainPage">
    <StackLayout>
        <!-- Place new controls here -->
        <Button Text="Button" HorizontalOptions="Center" VerticalOptions="CenterAndExpand"/>
    </StackLayout>
</ContentPage>

マテリアルデザインなボタンが配置されました。
image.png

ボタンがクリックしたことを検知

次はボタンクリックの検知です。MainPage.xmalに対応して、MainPage.xmal.csがあるので、おそらくイベント処理的なものはここに書いて行けば良さそうです。まずはボタンをクリックしたら呼ばれる関数を実装します。C#でのログ出力はSystem.Console.WriteLineを使います。

MainPage.xmal.cs
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using Xamarin.Forms;

namespace HelloToggleColor
{
    // Learn more about making custom code visible in the Xamarin.Forms previewer
    // by visiting https://aka.ms/xamarinforms-previewer
    [DesignTimeVisible(false)]
    public partial class MainPage : ContentPage
    {
        public MainPage()
        {
            InitializeComponent();
        }

        void OnButtonClicked(object sender, EventArgs args)
        {
            Console.WriteLine("DEBUG - Button Clicked!");
        }
    }
}

ボタンにはどの関数を呼び出せばいいかをClickedプロパティにセットしておきます。

MainPage.xmal
<?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:d="http://xamarin.com/schemas/2014/forms/design" xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" mc:Ignorable="d" x:Class="HelloToggleColor.MainPage">
    <StackLayout>
        <!-- Place new controls here -->
        <Button Text="Button"
                HorizontalOptions="Center"
                VerticalOptions="CenterAndExpand"
                Clicked="OnButtonClicked" />
    </StackLayout>
</ContentPage>

これでボタンを押すとログにDEBUG - Button Clicked!とでるアプリができました。
output17.gif

背景の色を変更

MainPage.xmal.csでロジックを書いて、MainPage.xmalでViewを書くことがわかったのであとは簡単です。
MainPage.xmal.csから参照できるように、MainPage.xmalの要素にNameプロパティを設定します。

MainPage.xmal
<?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:d="http://xamarin.com/schemas/2014/forms/design" xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" mc:Ignorable="d" x:Class="HelloToggleColor.MainPage">
    <StackLayout x:Name="layout">
        <!-- Place new controls here -->
        <Button Text="Button"
                HorizontalOptions="Center"
                VerticalOptions="CenterAndExpand"
                Clicked="OnButtonClicked" />
    </StackLayout>
</ContentPage>

Nameプロパティにセットした名前で直接アクセスできるので、BackgroundColorプロパティに色を代入するだけでおしまい。

using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using Xamarin.Forms;

namespace HelloToggleColor
{
    // Learn more about making custom code visible in the Xamarin.Forms previewer
    // by visiting https://aka.ms/xamarinforms-previewer
    [DesignTimeVisible(false)]
    public partial class MainPage : ContentPage
    {

        private bool toggle = true;

        public MainPage()
        {
            InitializeComponent();
        }

        void OnButtonClicked(object sender, EventArgs args)
        {
            Console.WriteLine("DEBUG - Button Clicked!");
            layout.BackgroundColor = toggle ? Color.Red : Color.Blue;
            toggle = !toggle;
        }
    }
}

output18.gif

はぇ〜、Android Studio + C#みたいな感じで開発しやすい。

Xamarin.Android

Xamarin.Androidも試していきます。
1.Visual Studioを開いて、新規で空のネイティブアプリ(iOS、Android)を選択します。
image.png
2.必要な情報を入力します。
image.png
3.これでプロジェクトが完成しました。
image.png

とりあえず、マークを押して実行します。
output19.gif

テンプレートなのに既にボタンもボタンイベントへの反応も実装されている!?

ボタンを実装

既にボタンは実装されますが、どこでボタンが実装されているのかは確認します。この画面のレイアウトはResources/layout/Main.axmlに定義されています。中ををみると、普通にAndroidのレイアウトファイルになっているいます。なので、ボタンが中央にある感じに直しちゃいます。

Main.axml
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:orientation="vertical"
    android:layout_width="match_parent"
    android:layout_height="match_parent">

    <Button android:id="@+id/myButton"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_centerInParent="true"
        android:text="@string/hello" />

</RelativeLayout>

ボタンがクリックしたことを検知

ボタンがクリックしたことを受け取るロジックは、MainActivity.csに書かれています。Javaのメソッド名のままC#に持ってきた感じがすごいします。interfaceの代わりにdelegateが使われてるのが特徴的です。

MainActivity.cs
using Android.App;
using Android.Widget;
using Android.OS;

namespace HelloToggleColor.Droid
{
    [Activity(Label = "HelloToggleColor", MainLauncher = true, Icon = "@mipmap/icon")]
    public class MainActivity : Activity
    {
        int count = 1;

        protected override void OnCreate(Bundle savedInstanceState)
        {
            base.OnCreate(savedInstanceState);

            // Set our view from the "main" layout resource
            SetContentView(Resource.Layout.Main);

            // Get our button from the layout resource,
            // and attach an event to it
            Button button = FindViewById<Button>(Resource.Id.myButton);

            button.Click += delegate {
                Android.Util.Log.Info("Xamarine","onClick");
            };
        }
    }
}

背景の色を変更

delegateのあたりをいじって従来のAndroid開発のノリでサクッと書きます。今回はボタンの親Viewが背景のRelativeLayoutだとわかっているので、親を取得してキャストして背景色を変えます。

MainActivity.cs
using Android.App;
using Android.Widget;
using Android.OS;

namespace HelloToggleColor.Droid
{
    [Activity(Label = "HelloToggleColor", MainLauncher = true, Icon = "@mipmap/icon")]
    public class MainActivity : Activity
    {
        private bool toggle = true;

        protected override void OnCreate(Bundle savedInstanceState)
        {
            base.OnCreate(savedInstanceState);

            // Set our view from the "main" layout resource
            SetContentView(Resource.Layout.Main);

            // Get our button from the layout resource,
            // and attach an event to it
            Button button = FindViewById<Button>(Resource.Id.myButton);

            button.Click += delegate {
                Android.Util.Log.Info("Xamarine","onClick");
                ((RelativeLayout)button.Parent).SetBackgroundColor((toggle)?Android.Graphics.Color.Red : Android.Graphics.Color.Blue);
                toggle = !toggle;
            };
        }
    }
}

output20.gif

Android Studio + C#な感じで開発しやすかった。

最後に

Web系の知識と、Android Studioでの開発がわかってると、色んなフレームワークをサクッと使っていける感じはありました。
今回試したフレームワークでも、普段より楽な点や面倒な点もあるので、やっぱり作るものに応じてという感じがします。
フレームワークによってはHello Worldアプリの実行まで10分もかからないものもあるので、Eclipseを使って開発していた頃とは違うし、めちゃくちゃ敷居は低くなったと思います。なので、Androidアプリを作りたいなぁとか思ってる人は、どれかフレームワークでも選んでサクッと作り始めるのがいいです。
もうちょっとフレームワークの利点・欠点の説明とか、フレームワークの仕組みとかにも触れて話していきたいので、随時更新していくのでよろしくお願いします。

Why not register and get more from Qiita?
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
Comments
No comments
Sign up for free and join this conversation.
If you already have a Qiita account
Why do not you register as a user and use Qiita more conveniently?
You need to log in to use this function. Qiita can be used more conveniently after logging in.
You seem to be reading articles frequently this month. Qiita can be used more conveniently after logging in.
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
ユーザーは見つかりませんでした