Androidアプリ開発

Spinnerのドロップダウンで
ナビゲーションバーを表示させない

この記事は約14分で読めます。
記事内に広告が含まれています。
スポンサーリンク

この記事は Androidスマホ用のアプリ開発の中で、
今後の開発で再使用性が高いと思われるコーディングをまとめたものです。
Java での開発経験、XML構文規則、Android のアプリ開発経験がある方を対象としています。
Android のアプリ開発でお役にたててれば、嬉しいです。
(これから Android のアプリ開発や Java での開発を始めたい方への案内は、
記事の最後で紹介します)

この記事のテーマ


ドロップダウンリスト(Spinner)のドロップダウンでナビゲーションバーを表示させない

ポイント

ドロップダウンリスト(Spinner)は内部的にListPopupWindowで実装されています。
Activityから独立したWindowを持っているため、ドロップダウンが表示されるとWindowにフォーカスが当たります。
SystemUiVisibilityなどのフラグは、このWindowが優先されるためナビゲーションバーが表示されます。

これを回避するためには、SpinnerがもつWindowにフォーカスが当たらないようにする必要がありますが、Spinnerが持つWindowを直接操作できません。
今回は、リフレクションを使って、
Spinnerが持つWindowにフォーカスが当たらない方法を紹介します。

この現象は、全画面モードでシステムバーを非表示としている場合に発生します。
全画面モードの有効化(システムバーの非表示&透明化)はこちらで紹介しています↓↓↓

システムバーを表示させないカスタムSpinner

ドロップダウンリスト(androidx.appcompat.widget.AppCompatSpinner)を継承したCustomSpinnerを作成します。
SpinnerのWindowにフォーカスが当たらないように、リフレクションを使って、SpinnerのもつPopupWindowを操作するメソッドを実装します。

Spinner表示ではナビゲーションバーが非表示。Dialog表示ではナビゲーションバーが表示

カスタムクラス(CustomSpinner)

コンストラクタは、継承元のSpinnerと同じものをすべて用意します。
SpinnerのもつWindowを操作するには、リフレクションを使って、Spinnerがもつ2つの層に渡って、順にアクセスする必要があります。
getDeclaredFieldでmPopupをもつFieldを取得し、setAccessibleで次の層をアクセス許可します。
同様の方法で次の層にアクセスし、
SpinnerのもつPopupWindowを取得します。
取得した
PopupWindowに対して、setFocusablefalseをセットします。

…
import android.util.AttributeSet;
import android.widget.PopupWindow;
import androidx.appcompat.widget.AppCompatSpinner;
import androidx.appcompat.widget.ListPopupWindow;
import java.lang.reflect.Field;
…
public class CustomSpinner extends androidx.appcompat.widget.AppCompatSpinner {
    public CustomSpinner(Context context) {
        super(context);
    }
    public CustomSpinner(Context context, AttributeSet attrs) {
        super(context, attrs);
    }
    public CustomSpinner(Context context, AttributeSet attrs, int defStyle) {
        super(context, attrs, defStyle);
    }
    …

    @SuppressLint("DiscouragedPrivateApi")
    public static void avoidSpinnerDropdownFocus(AppCompatSpinner spinner) {
        try {
            Field listPopupField = AppCompatSpinner.class.getDeclaredField("mPopup");
            listPopupField.setAccessible(true);
            Object listPopup = listPopupField.get(spinner);
            if (listPopup instanceof ListPopupWindow) {
                Field popupField = ListPopupWindow.class.getDeclaredField("mPopup");
                popupField.setAccessible(true);
                Object popup = popupField.get((ListPopupWindow) listPopup);
                if (popup instanceof PopupWindow) {
                    ((PopupWindow) popup).setFocusable(false);
                }
            }
        } catch (NoSuchFieldException | IllegalAccessException e) {
            e.printStackTrace();
        }
    }
}

AppCompatSpinnerを使用する場合は、androidx.appcompat.widget.ListPopupWindowを使用します。

(参考)AppCompatSpinner

…
public class AppCompatSpinner extends Spinner implements TintableBackgroundView {
    @SuppressLint("ResourceType")
    @StyleableRes
    private static final int[] ATTRS_ANDROID_SPINNERMODE = {android.R.attr.spinnerMode};
    private static final int MAX_ITEMS_MEASURED = 15;
    private static final String TAG = "AppCompatSpinner";
    private static final int MODE_DIALOG = 0;
    private static final int MODE_DROPDOWN = 1;
    private static final int MODE_THEME = -1;
    private final AppCompatBackgroundHelper mBackgroundTintHelper;

    /** Context used to inflate the popup window or dialog. */
    private final Context mPopupContext;

    /** Forwarding listener used to implement drag-to-open. */
    private ForwardingListener mForwardingListener;

    /** Temporary holder for setAdapter() calls from the super constructor. */
    private SpinnerAdapter mTempAdapter;
    private final boolean mPopupSet;
    private SpinnerPopup mPopup;
  …

カスタムクラスの使用方法

カスタムクラスの使用方法は、Spinnerを指定していた部分を、CustomSpinnerに置き換えます。
インスタンス化したCustomSpinnerを引数にavoidSpinnerDropdownFocusに呼び出します。

        :     
        CustomSpinner type = view.findViewById(R.id.type);
        ArrayAdapter<String> adapter = new ArrayAdapter<>(context, R.layout.item_spinner, getResources().getStringArray(R.array.array_type));
        adapter.setDropDownViewResource(R.layout.item_spinner);
        type.setAdapter(adapter);
        type.setOnItemSelectedListener(new AdapterView.OnItemSelectedListener() {
            @Override
            public void onItemSelected(AdapterView<?> parent, View view, int position, long id) {
                 // 選択時の処理
            }
            @Override
            public void onNothingSelected(AdapterView<?> parent) {
                // 処理があれば、ここに記述する
            }
        });
        CustomSpinner.avoidSpinnerDropdownFocus(type);
        :  

◎レイアウト(fragment.xml)

                :
                <com.xxx.xxxx.CustomSpinner
                    android:id="@+id/type"
                    android:layout_width="wrap_content"
                    android:layout_height="wrap_content"
                    android:entries="@array/array_type" />
                :

◎レイアウト(item_spinner.xml)

<TextView
    xmlns:android="http://schemas.android.com/apk/res/android"
    android:id="@+id/name"
    android:layout_width="match_parent"
    android:layout_height="30dp"
    android:textSize="14sp"
    android:textColor="@color/white"
    style="?android:attr/spinnerDropDownItemStyle"/>

◎strings.xml

    :
    <string-array name="array_type">
        <item>×1</item>
        <item tools:ignore="TypographyFractions">×1/2</item>
        <item tools:ignore="TypographyFractions">×1/4</item>
        <item>×1/8</item>
    </string-array>
    :

今回は、ここまでです。

ドロップダウンリスト(Spinner)の表示でナビゲーションバーを非表示にしているAndroidアプリです。
チャプタや再生速度を選択するドロップダウンリストで使用しています。

スマホスタンドはアプリ開発の必需品と言って良いでしょう♪

誤字脱字、意味不明でわかりづらい、
もっと詳しく知りたいなどのご意見は、