Cocos2d-xでテキストボックスのUIテーマをMaterial Designにする方法

## ターゲット
- Cocos2d-x v3.8未満のバージョンを使用しているアプリ制作者
  - v3.8だとテキストボックス関係のクラスが見直されているので修正されている可能性があります

## 概要
- Cocos2d-x v3.7以下のバージョンで、Android API Levelに応じてテキストボックスのUIテーマを切り替えられるようにしました
- Cocos2dxEditBoxDialogクラスのコンストラクタでUIテーマをハードコーディングしているのでそこを修正すれば変更可能
  - style.xmlでは切り替わらなかったのでコードによる変更

## 背景
### Cocos2d-xのテキストボックスダイアログ
Cocos2d-x v3.7以下のバージョンでのテキストボックスはAndroidバージョンに関わらず古いテーマが使用されている。

![s_old_ui_01.png](https://qiita-image-store.s3.amazonaws.com/0/78128/0e5fd2da-58cd-ce56-dad8-50c4248427a7.png "s_old_ui_01.png")


### マテリアルデザインについて
Googleによると、Android 5.0以降ではアプリのUIテーマにマテリアルデザインを使用することを推奨しています。
参考: [ Android Developers:デベロッパーのためのマテリアル デザイン](https://developer.android.com/training/material/index.html)

### マテリアルテーマを適用する
Android 5.0以降のみに提供するアプリであれば、res/styles.xmlで記述しているMyThemeで
```android:Theme.Material```などを継承することでマテリアルテーマが適用されます。
参考: [ Android Developers:マテリアル テーマの使用](https://developer.android.com/training/material/theme.html)

### OSの互換性を持たせる
アプリによってはAndroid 4系にも提供していると思います。
その場合、4ではマテリアルテーマが存在しないのでOSバージョンによってテーマを変更する必要があります。
参考: [ Android Developers:互換性の維持](https://developer.android.com/training/material/compatibility.html)

詳しいやり方はここが参考になります。いくつか方法はあります。
->[既存のAndroidアプリにMaterial Themeを適用する方法](https://firespeed.org/diary.php?diary=kenz-1790)

## 実際に適用してみる
Android 6.0端末で行いました。
私はstyles.xmlをAndroid 5用と4以下用の2つ用意し、
それをApplicationのAndroidManifestにstylesで定義したAppThemeを適用する方法を試しました。

```res/values/styles.xml
<?xml version="1.0" encoding="utf-8"?>
<resources>
    <style name="AppTheme" parent="@android:style/Theme.Holo.Light">
    </style>
</resources>
```

```res/values-v21/styles.xml
<?xml version="1.0" encoding="utf-8"?>
<resources>
    <style name="AppTheme" parent="android:Theme.Material.Light">
    </style>
</resources>
```

上記適用することで↓が

![s_old_ui.png](https://qiita-image-store.s3.amazonaws.com/0/78128/414a175a-1d7f-9582-faa2-58964e70a144.png "s_old_ui.png")


こうなる予定↓だったが…

![s_new_ui.png](https://qiita-image-store.s3.amazonaws.com/0/78128/7cf65a5c-50cc-5120-ec39-fb097a820339.png "s_new_ui.png")


…ならなかった。styles.xmlが効いていない模様。

ただ、Android 7でやると古いUIテーマのままなはずなのに新UIテーマで表示される。
7以降はOSがよしなにやってくれているのかもしれない。
とは言えAndroid6では古いままなので何とかする。

## 原因調査: Cocos2d-xのコードを見てみる
### C++
テキストボックス(入力インターフェース)はcocos2d_libsの```EditBox```クラスで定義している。
このクラスで定義されたテキストボックスをタップするとAndroidでは
```EditBoxImplAndroid::openKeyboard()```内の```showEditTextDialogJNI```メソッドが呼ばれる。
このメソッドで画面上部のテキストボックスダイアログを表示させているようだ。

```CCEditBoxImplAndroid.cpp
void EditBoxImplAndroid::openKeyboard()
{
    if (_delegate != NULL)
    {
        _delegate->editBoxEditingDidBegin(_editBox);
    }

	// .......

    showEditTextDialogJNI(_placeHolder.c_str(),
                          _text.c_str(),
                          static_cast<int>(_editBoxInputMode),
                          static_cast<int>(_editBoxInputFlag),
                          static_cast<int>(_keyboardReturnType),
                          _maxLength,
                          editBoxCallbackFunc,
                          static_cast<void*>(this));
}
```

### Java
Java側ではActivityにある```showEditTextDialog```が呼ばれます(JNIのところは省きます)。

```Cocos2dxActivity.java
@Override
public void showEditTextDialog(final String pTitle, final String pContent, final int pInputMode, final int pInputFlag, final int pReturnType, final int pMaxLength) { 
    Message msg = new Message();
    msg.what = Cocos2dxHandler.HANDLER_SHOW_EDITBOX_DIALOG;
    msg.obj = new Cocos2dxHandler.EditBoxMessage(pTitle, pContent, pInputMode, pInputFlag, pReturnType, pMaxLength);
    this.mHandler.sendMessage(msg);
}
```

実際にダイアログを作っているところはCocos2dxHandlerの```showEditBoxDialog```。

```Cocos2dxHandler.java
private void showEditBoxDialog(Message msg) {
    EditBoxMessage editBoxMessage = (EditBoxMessage)msg.obj;
    new Cocos2dxEditBoxDialog(this.mActivity.get(),
            editBoxMessage.title,
            editBoxMessage.content,
            editBoxMessage.inputMode,
            editBoxMessage.inputFlag,
            editBoxMessage.returnType,
            editBoxMessage.maxLength).show();
}
```

そして、最後。cocos2d-xのEditBoxDialogクラスのコンストラクタ。

```Cocos2dxEditBoxDialog.java
public Cocos2dxEditBoxDialog(final Context pContext, final String pTitle, final String pMessage, final int pInputMode, final int pInputFlag, final int pReturnType, final int pMaxLength) {        
     super(pContext, android.R.style.Theme_Translucent_NoTitleBar_Fullscreen);     
//   super(context, R.style.Theme_Translucent);        
   
     this.mTitle = pTitle;     
     this.mMessage = pMessage;     
     this.mInputMode = pInputMode;     
     this.mInputFlag = pInputFlag;     
     this.mReturnType = pReturnType;       
     this.mMaxLength = pMaxLength;     
}
```

### テキストボックスのテーマが変わらない原因
```Cocos2dxEditBoxDialog.java``` で古いテーマがハードコーディングされています。

```
super(pContext, android.R.style.Theme_Translucent_NoTitleBar_Fullscreen);
```

参考: [Cocos2dxEditBoxDialog.java#L140](https://github.com/cocos2d/cocos2d-x/blob/2210d1fe87c3dd7d2ce79d01945b85a0de24862d/cocos/platform/android/java/src/org/cocos2dx/lib/Cocos2dxEditBoxDialog.java#L140)

styles.xmlをいくらいじってもダメだった理由が分かりました。
ちなみにこのあたりのクラスはCocos2d-x v3.8で見直されています。
https://github.com/cocos2d/cocos2d-x/pull/13273

v3.8ではCocos2dxEditBoxDialogクラスは無くなり、Cocos2dxEditBoxクラスになっています。
テーマ直書きもなくなっています。
この状態であればstyles.xmlで定義したAppThemeが適用されるのではないかと思います。
(手間かかるので試してないです。)

```Cocos2dxEditBox.java
public  Cocos2dxEditBox(Context context){
     super(context);

     this.setFocusable(true);
     this.setFocusableInTouchMode(true);

     this.setInputFlag(kEditBoxInputFlagInitialCapsAllCharacters);
     this.setInputMode(kEditBoxInputModeSingleLine);
     this.setReturnType(kKeyboardReturnTypeDefault);
     this.setHintTextColor(Color.GRAY);
     this.setVisibility(View.INVISIBLE);
     this.setBackgroundColor(Color.TRANSPARENT);
     this.setTextColor(Color.WHITE);
     this.setSingleLine();
}
```

## 解決方法: テキストボックスのUIテーマをMaterial Designにする
本題です。テキストボックスのUIテーマをMaterial Designにしていきます。
まず、showEditBoxDialog内でAPI Levelからテーマを変更します。

```Cocos2dxHandler.java
private void showEditBoxDialog(Message msg) {
	 
	 // テーマをAPI Levelで変更する(互換性維持)
    int theme = 0;
    if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
        // Android 5以上
        theme = android.R.style.Theme_Material_NoActionBar_Fullscreen;
    } else {
        // Android 4以下
        theme = android.R.style.Theme_Translucent_NoTitleBar_Fullscreen;
    }
    
    EditBoxMessage editBoxMessage = (EditBoxMessage)msg.obj;
    
    // API Levelにあったthemeを使ってCocos2dxEditBoxDialogを生成
    new Cocos2dxEditBoxDialog(this.mActivity.get(),
            editBoxMessage.title,
            editBoxMessage.content,
            editBoxMessage.inputMode,
            editBoxMessage.inputFlag,
            editBoxMessage.returnType,
            editBoxMessage.maxLength,
            theme).show();
}
```

Cocos2dxEditBoxDialogクラスではAPI Levelにあったテーマを使うように変更。

```Cocos2dxEditBoxDialog.java
public Cocos2dxEditBoxDialog(final Context pContext, final String pTitle, final String pMessage, final int pInputMode, final int pInputFlag, final int pReturnType, final int pMaxLength, final int theme) {
    // API Levelにあったthemeを使う
    super(pContext, theme);
    // super(pContext, android.R.style.Theme_Translucent_NoTitleBar_Fullscreen);

    this.mTitle = pTitle;
    this.mMessage = pMessage;
    this.mInputMode = pInputMode;
    this.mInputFlag = pInputFlag;
    this.mReturnType = pReturnType;
    this.mMaxLength = pMaxLength;
}
```

同じくCocos2dxEditBoxDialogクラスのonCreateでテキストボックスの背景色を変更します。
これをやらないと背景が透明になって見づらかった。。

```Cocos2dxEditBoxDialog.java
@Override
protected void onCreate(final Bundle pSavedInstanceState) {
    super.onCreate(pSavedInstanceState);

    // ......

    this.mInputEditText = new EditText(this.getContext());
    final LinearLayout.LayoutParams editTextParams = new LinearLayout.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.WRAP_CONTENT);
    editTextParams.leftMargin = editTextParams.rightMargin = this.convertDipsToPixels(10);
    // 背景色を変更
    this.mInputEditText.setBackgroundColor(0xf0ffffff);
    layout.addView(this.mInputEditText, editTextParams);

    // ......
}
```

## 結果
無事マテリアルデザインになりました。

![s_android6.png](https://qiita-image-store.s3.amazonaws.com/0/78128/57ffaf3e-4cf8-380a-2e95-515eb68a7fff.png "s_android6.png")

## まとめ
- Cocos2d-x v3.7以下のバージョンではstyles.xmlでテキストボックスのUIテーマが変更できなかった
- Cocos2dxEditBoxDialogクラスで指定していたテーマをAPI Levelによって変更できるようにした
  - style.xmlではなくコードによる変更