Android4.3/4.4におけるImageView#bringToFrontとViewGroup#bringChildToFrontの違い

こんにちは。
Androidアプリ開発において、API Levelの違いに手を焼かされることが多々あると思います。今回は、以下のような要件の際に悩まされたことを踏まえて事例紹介いたします。

#ことの発端#

複数の重なって表示している画像（ImageView）がある。
ユーザによるアクション（何かをクリックなど）で背面にあった画像を前面（Zオーダー）にしたい。

最初は黒イド(kroid)君が前面に居ます。

![bring1.png](https://qiita-image-store.s3.amazonaws.com/0/100612/9cffa2cc-b9c5-1a53-bac4-8ee28e7a6697.png)

切替ボタンをクリックしたら、

![bring2.png](https://qiita-image-store.s3.amazonaws.com/0/100612/3103ad26-9612-9e46-08cd-dcacad40be50.png)

レッドロイド(redorind)君が前面に来てほしいのです。

しかし、`ImageView`クラスの`bringToFront()`メソッドをちゃんと書いているのに、前面に来てくれない！なんで？どうして？なんなのよ私のJelly Bean？！

#結論#

`android.view.ViewGroup`クラスの`bringChildToFront(View child)`メソッドの実装が、あるAPI Levelを境に、異なっていました。事件はImageViewクラスのbringToFront()メソッドで起きている！のではなかったのでした。

###API Level 18 [Android 4.3] Jelly Bean###

```java:ViewGroup.java
public void bringChildToFront(View child) {
    int index = indexOfChild(child);
    if (index >= 0) {
        removeFromArray(index);
        addInArray(child, mChildrenCount);
        child.mParent = this;
    }
}
```
###API Level 19 [Android 4.4] KitKat###

```java:ViewGroup.java
public void bringChildToFront(View child) {
    int index = indexOfChild(child);
    if (index >= 0) {
        removeFromArray(index);
        addInArray(child, mChildrenCount);
        child.mParent = this;
        requestLayout();
        invalidate();
    }
}
```

無かったものが有って、有るものが無かった。

KitKat以上のAPIドキュメントにも、以下の記載がありました。

[View#bringToFront()](https://developer.android.com/reference/android/view/View.html#bringToFront())
[ViewGroup#bringChildToFront(View)](https://developer.android.com/reference/android/view/View.html#bringToFront())

> Prior to KITKAT this method should be followed by calls to **requestLayout()** and **invalidate()** on this parent to force the parent to redraw with the new child ordering.

#なぜ、この事例で悩んだのか#

2017年4月現在、Androidのバージョンは7.1がリリースされています。
そこで、API Level 25でアプリ開発をして、なおかつAndroid 7.1の端末（やエミュレータ）で動作確認しても、ImageViewが意図したとおりに前面になってくれます。

しかし、Android 4.3以下の端末だと、アララララ？となりました。

2017年3月現在、Androidの各バージョンのシェアは、Android 6.0が約50%を占めています。Android 5.0が約20%。Android 4.4は約10%。二桁はこの3つ。KitKat以上で約9割も占めているこのご時世。
さて問題のJelly Bean以下ですが、

- Android 4.3 … 0.2%
- Android 4.2 … 3.6%
- Android 4.1 … 1.2%
- Android 4.0.3 … 0.6%

というように、物持ちのいい人はいるのです。

minSdkVersionは19以上にしか設定しない！とすれば、こんな事例に悩まないで済むのですが。

#サンプルプログラム#

API Level 25で作ってみました。

```xml:activity_main.xml
<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" >

    <Button
        android:id="@+id/bringToFrontButton"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:onClick="onClickBringToFrontButton"
        android:text="切替" />

    <ImageView
        android:id="@+id/redroid"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_below="@+id/bringToFrontButton"
        android:src="@drawable/redroid" />

    <ImageView
        android:id="@+id/blackroid"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_below="@+id/bringToFrontButton"
        android:src="@drawable/blackroid" />

</RelativeLayout>
```

黒イド君はblackroid.pngで、レッドロイド君はredroid.pngとして/res/drawableに入れてあります。私はここにきて、この「黒イド君」と「レッドロイド君」という駄洒落が気に入ってきました。

```java:MainActivity.java
package jp.co.casareal.ls.bringtofront;

import android.app.Activity;
import android.os.Bundle;
import android.view.View;
import android.widget.ImageView;

public class MainActivity extends Activity {

	ImageView redroid;
	ImageView blackroid;
	boolean frontFlag = true;

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

		redroid = (ImageView) findViewById(R.id.redroid);
		blackroid = (ImageView) findViewById(R.id.blackroid);
	}

	public void onClickBringToFrontButton(View view) {
		if (frontFlag) {
			redroid.bringToFront();
		} else {
			blackroid.bringToFront();
		}
		frontFlag = !frontFlag;
		// ※
	}
}
```
KitKat以上の端末なら、ボタンをクリックすれば、俺が！オレが！で黒イド君とレッドロイド君が前へ前へと出てくれるんですが。KitKat未満の端末だと、レッドロイド君は黒イド君の後ろに隠れっぱなしのTOO SHY SHY BOY!のままです。

##KitKat未満の対応##

上記のActivityサブクラスの※印の箇所に、以下のコードを追記します。

```java:追記コード
		if (VERSION.SDK_INT < VERSION_CODES.KITKAT) {
			RelativeLayout rl = (RelativeLayout) findViewById(R.id.mainLayout);
			rl.requestLayout();
			rl.invalidate();
		}
```

#まとめ#

上記の対応以外にも、様々な方法があるかとは思います。

この記事にてお伝えしたかったことは、開発中のAndroidアプリのビルド・ターゲットのAPI Levelを最新のに変える際には気を付けましょう！ということでした。

以上です。