[Android Tips]RadioGroupでカーナビみたいなメニュー作りにチャレンジ!!

radio_sample_eye_catch

この記事は公開されてから1年以上経過しています。情報が古い可能性がありますので、ご注意ください。

はじめに

Androidで、画面に収まらない数のメニューを管理する場合には「ListView」を使ってスクロールさせるのが常套手段ですが、今回は、一昔前を思い出してリストの項目が一つずつ動くカーナビのメニューのようなViewを作ってみました。

こんな風に作りました

radio_sample "NEXT"ボタンを押すと、次の項目が表示され、"PREV"ボタンを押すと前の項目が表示されます。
画面右側、上部にある"CLEAR"ボタンを押すと、選択状態が解除されます。

用意するもの

基本的には、Androidアプリケーションプロジェクトのファイル群以外に必要なものはありませんが、今回は、次のようなボタン用の画像を用意しました。

btn
通常状態
btn_pressed
タップされた/選択された


この画像をAndroid Assets StudioSimple nine-patch generatorを使って解像度毎のボタン背景を生成しておきます。

実装の内容

  • ListViewは使わない
  • ScrollViewも使わない
  • Viewは動かさずに見かけ上動いているように見せる
  • RadioGroupで単一選択状態をつくる

解説

1.レイアウト


まずは画面のレイアウトを作成します。
全体の構成はこんな感じになっています。 000
とてもシンプルな構成です。 実際のxmlはこちらです↓↓↓

res/layout/activity_main.xml

<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    android:id="@+id/LinearLayout1"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:background="#778899"
    android:baselineAligned="false"
    android:orientation="horizontal"
    tools:context="dev.classmethod.radiogroupsample.MainActivity" >

    <!-- 選択項目一覧-->
    <RadioGroup
        android:id="@+id/radio_group"
        android:layout_width="0dp"
        android:layout_height="match_parent"
        android:layout_weight="1"
        android:orientation="vertical"
        android:padding="16dp" >

        <RadioButton
            android:id="@+id/item_01"
            style="@style/ItemStyle"
            android:layout_marginBottom="4dp"
            android:layout_weight="1" />

        <RadioButton
            android:id="@+id/item_02"
            style="@style/ItemStyle"
            android:layout_marginBottom="4dp"
            android:layout_weight="1" />

        <RadioButton
            android:id="@+id/item_03"
            style="@style/ItemStyle"
            android:layout_marginBottom="4dp"
            android:layout_weight="1" />

        <RadioButton
            android:id="@+id/item_04"
            style="@style/ItemStyle"
            android:layout_marginBottom="4dp"
            android:layout_weight="1" />

        <RadioButton
            android:id="@+id/item_05"
            style="@style/ItemStyle"
            android:layout_weight="1" />
    </RadioGroup>

    <FrameLayout
        android:layout_width="144dp"
        android:layout_height="match_parent"
        android:background="#888" >

        <View
            android:layout_width="4dp"
            android:layout_height="match_parent"
            android:background="@drawable/shadow" />

        <LinearLayout
            android:layout_width="match_parent"
            android:layout_height="match_parent"
            android:orientation="vertical"
            android:padding="16dp" >

            <!-- 選択解除ボタン -->
            <Button
                android:id="@+id/btn_clear"
                style="@style/BtnStyle"
                android:layout_marginBottom="4dp"
                android:layout_weight="1"
                android:text="CLEAR" />

            <!-- 選択中の項目名を表示するTextView -->
            <TextView
                android:id="@+id/text_view"
                android:layout_width="match_parent"
                android:layout_height="0dp"
                android:layout_marginBottom="8dp"
                android:layout_weight="2"
                android:background="#778899"
                android:gravity="center"
                android:textColor="#fff"
                android:textSize="24sp" />

            <!-- 前へボタン -->
            <Button
                android:id="@+id/btn_prev"
                style="@style/BtnStyle"
                android:layout_marginBottom="4dp"
                android:layout_weight="1"
                android:text="PREV" />

            <!-- 次へボタン -->
            <Button
                android:id="@+id/btn_next"
                style="@style/BtnStyle"
                android:layout_weight="1"
                android:text="NEXT" />
        </LinearLayout>
    </FrameLayout>

</LinearLayout>


同じパラメータを持つウィジェットはスタイルを指定してコード量を減らします。
プロジェクトフォルダ内の res/values/style.xml にカスタムスタイルを作成します。
ここでは、RadioGroupのRadioButtonのスタイルとButtonのスタイルを定義しています。

res/value/style.xml

<style name="ItemStyle" parent="@android:style/Widget.CompoundButton">
        <item name="android:layout_width">match_parent</item>
        <item name="android:layout_height">0dp</item>
        <item name="android:background">@drawable/radio_stateful</item>
        <item name="android:textColor">@drawable/radio_text_color</item>
        <item name="android:button">@null</item>
        <item name="android:textSize">24sp</item>
        <item name="android:gravity">left|center_vertical</item>
        <item name="android:paddingLeft">24dp</item>
    </style>

    <style name="BtnStyle" parent="@android:style/Widget.Button">
        <item name="android:layout_width">match_parent</item>
        <item name="android:layout_height">0dp</item>
        <item name="android:background">@drawable/btn_stateful</item>
        <item name="android:textColor">@drawable/btn_text_color</item>
        <item name="android:textSize">18sp</item>
    </style>
    


更に、各ウィジェットの状態に応じた背景と文字色を定義します。

res/drawable/radio_stateful.xml

<selector xmlns:android="http://schemas.android.com/apk/res/android">
    <!-- 選択されている状態 -->
    <item android:drawable="@drawable/btn_pressed" android:state_checked="true"/>
    <!-- 選択されていない状態 -->
    <item android:drawable="@drawable/btn" android:state_checked="false"/>
</selector>

res/drawable/radio_text_color.xml

<selector xmlns:android="http://schemas.android.com/apk/res/android">
    <!-- 選択されている状態 -->
    <item android:state_checked="true" android:color="#fff"/>
    <!-- 選択されていない状態 -->
    <item android:state_checked="false" android:color="#777"/>
</selector>

res/drawable/btn_stateful.xml

<selector xmlns:android="http://schemas.android.com/apk/res/android">
    <!-- ボタンが押されている状態 -->
    <item android:drawable="@drawable/btn_pressed" android:state_pressed="true"/>
    <!-- ボタンが押されていない状態 -->
    <item android:drawable="@drawable/btn" android:state_pressed="false"/>
</selector>

res/drawable/btn_text_color.xml

<selector xmlns:android="http://schemas.android.com/apk/res/android">
  <!-- ボタンが押されている状態 -->
    <item android:state_pressed="true" android:color="#fff"/>
    <!-- ボタンが押されていない状態 -->
    <item android:state_pressed="false" android:color="#777"/>

</selector>

2.動きを作る


冒頭のデモのような動きを作ります。
まず、前提として東京23区の名称が配列に入っています。
そして、リストのアイテムが選択されると、対象の区名称が配列のどこに位置しているかを記憶し、それを元にして"NEXT"または"PREV"ボタンが押される度にRadioButtonに区名称、選択の有無をセットしています。

import android.app.Activity;
import android.os.Bundle;
import android.view.View;
import android.view.View.OnClickListener;
import android.widget.Button;
import android.widget.RadioButton;
import android.widget.RadioGroup;
import android.widget.TextView;
import android.widget.RadioGroup.OnCheckedChangeListener;

public class MainActivity extends Activity {
    

    /** 初期化用の数字. */
    private final int INIT_NUM = 99;

    /** 選択されたアイテムのindex. */
    private int mSelectedIndex = INIT_NUM;

    /** スクロールのカウント. */
    private int mScrollCount = 0;

    /** アイテム選択の可否を管理するフラグ. */
    private boolean isEnableSelect = true;

    /** 選択されたアイテムのposition. */
    private int mSelectedPosition = 0;
    
    /** リストアイテムに表示するラベルのリスト */
    private String[] mLabelList;

    /** チェックされたRadioButtonのindex. */
    private int mRadioPosition = 0;

    /** RadioGroup. */
    private RadioGroup mRadioGroup;

    /** RadioButtonのリスト. */
    private RadioButton[] mRadioButtonList;
    
    /** 選択中のアイテムを表示するTextView. */
    private TextView mTextview;
    

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

        initView();

        initItems();

        initRadioButton();

    }

    
    /**
     * 東京23区の名前を取り込む.
     */
    private void initItems(){

        mLabelList = getResources().getStringArray(R.array.name_array);

    }

    /**
     * Viewのインスタンスを取得する.<br>
     * 各Listenerをセットする.
     */
    private void initView(){

        mRadioGroup = (RadioGroup)findViewById(R.id.radio_group);
        mRadioGroup.setOnCheckedChangeListener(mCheckedChangeListener);

        Button nextButton = (Button)findViewById(R.id.btn_next);
        nextButton.setOnClickListener(mNextClickListener);

        Button prevButton = (Button)findViewById(R.id.btn_prev);
        prevButton.setOnClickListener(mPrevClickListener);

        Button clearButton = (Button)findViewById(R.id.btn_clear);
        clearButton.setOnClickListener(mClearClickListener);
        
        mTextview = (TextView)findViewById(R.id.text_view);

    }

    /**
     * RadioButtonを初期化する.
     */
    private void initRadioButton(){

        // リソースからViewのID配列を取得する
        // この時点では文字列の配列で取得します
        String[] ids = getResources().getStringArray(R.array.item_id_array);

        mRadioButtonList = new RadioButton[ids.length];

        for (int i = 0; i < ids.length; i++) {

            // IDをintで取得
            int id = getResources().getIdentifier(ids[i], "id", getPackageName());

            // Viewのインスタンスを取得
            mRadioButtonList[i] = (RadioButton)findViewById(id);
            
            // Viewの位置に対応したラベルをセット
            mRadioButtonList[i].setText(mLabelList[i]);

        }

    }

    /**
     * 選択を解除する.
     */
    private void clear(){
        
        isEnableSelect = false;
        mRadioGroup.clearCheck();
        isEnableSelect = true;
        mSelectedIndex = INIT_NUM;
        
        mTextview.setText("");
  
    }

    /**
     * リストアイテムにラベルをセットする.
     */
    private void setItemName(){
        
        for (int i = 0; i < mRadioButtonList.length; i++) {
            
            int position = i + mScrollCount;
            
            mRadioButtonList[i].setText(mLabelList[position]);

            if(position == mSelectedIndex){
                isEnableSelect = false;
                mRadioButtonList[i].setChecked(true);
                isEnableSelect = true;
            }

        }

        if(mSelectedPosition < 0 || mSelectedPosition == mRadioButtonList.length){
            isEnableSelect = false;
            mRadioGroup.clearCheck();
            isEnableSelect = true;
        }

    }

    /**
     * RadioGroupのチェック状態の変化を監視する.
     */
    private OnCheckedChangeListener mCheckedChangeListener = new OnCheckedChangeListener() {

        @Override
        public void onCheckedChanged(RadioGroup group, int checkedId) {

            // 選択されたアイテムの位置を取得する
            mRadioPosition = group.indexOfChild(findViewById(group.getCheckedRadioButtonId()));
            
            if(isEnableSelect){
                mSelectedIndex = mScrollCount + mRadioPosition;
                mTextview.setText(mLabelList[mSelectedIndex]);
            }

        }
    };

    /**
     * CLEARボタンのClickListener.
     */
    private OnClickListener mClearClickListener = new OnClickListener() {

        @Override
        public void onClick(View v) {
            
            // RadioGroupの選択状態を解除する
            clear();
        
        }

    };

    /**
     * PREVボタンのClickListener.
     */
    private OnClickListener mPrevClickListener = new OnClickListener() {

        @Override
        public void onClick(View v) {
            
            if(mScrollCount > 0){
                mScrollCount--;
                mSelectedPosition = mRadioPosition + 1;
                setItemName();
            } 
        }
        
    };

    /**
     * NEXTボタンのClickListener.
     */
    private OnClickListener mNextClickListener = new OnClickListener() {

        @Override
        public void onClick(View v) {
            
            if(mScrollCount < mLabelList.length - mRadioButtonList.length){
                mScrollCount++;
                mSelectedPosition = mRadioPosition - 1;
                setItemName();
            }
        
        }
    };

}

res/value/array.xml

<resources>
	
    <!-- 東京23区の名前 -->
    <string-array name="name_array">
        <item>千代田区</item>
        <item>中央区</item>
        <item>港区</item>
        <item>新宿区</item>
       		・
       		・
       		・
        <item>練馬区</item>
        <item>足立区</item>
        <item>葛飾区</item>
        <item>江戸川区</item>
    </string-array>
    
    <!-- RadioButtonのID -->
    <string-array name="item_id_array">
        <item>item_01</item>
        <item>item_02</item>
        <item>item_03</item>
        <item>item_04</item>
        <item>item_05</item>
    </string-array>

</resources>

まとめ

このような動きのものを作るときに、今回実践した方法が適したやりかたなのかはさておいて、 このくらいのものであればRadioGroupで動きを作ることが出来るとわかりました。

ヌルヌル動く今風の画面もいいですが、落ち着いてぱたぱた動くリストもなかなかいいもんだなと感じたチャレンジでした。