【Android】キーボード出現時にListViewを自動スクロールさせる


エディットテキストのフォーカス等でキーボードが出現した際に、ListViewを任意の場所（アイテム）に自動スクロールさせる方法です。

わかりづらいですが完成形はこんな感じ。

　　![s_qiita_2_1.png](https://qiita-image-store.s3.amazonaws.com/0/53088/a1ec9366-c16b-9a6f-53ce-9a4e61d1e13c.png)　　![s_qiita_2_2.png](https://qiita-image-store.s3.amazonaws.com/0/53088/200d44b9-c9ff-a1ca-4980-82859e5e36f1.png)


##キーボード出現を検知する

View#onSizeChangedをフックし、このメソッドが取得するViewのサイズ変更をキーボード出現とみなす方法をとります。
サイズ変更をActivityやFragmentでハンドリングできるようにするために、まずはListViewを拡張したクラスをつくります。 
以下ではonSizeChangedがコールバックされたらリスナーのonKeyboadAppearedを呼び出すようにしています。


```java:CustomListView.java

package com.example.scrollsample;

import android.content.Context;
import android.util.AttributeSet;
import android.widget.ListView;

public class CustomListView  extends ListView{

	//ActivityまたはFragmentが実装するインターフェース
	public interface OnKeyboardAppearedListener {
		public void onKeyboardAppeared(boolean isChange);
	}
	private OnKeyboardAppearedListener listener;


	public CustomListView(Context context) {
		super(context);
	}

	//このコンストラクタが無いとXMLからインフレートできないので注意
	public  CustomListView(Context context, AttributeSet attrs) {
		super(context, attrs);
	}

	public  CustomListView(Context context, AttributeSet attrs,
			int defStyle) {
		super(context, attrs, defStyle);
	}

	//リスナー登録のメソッド
	public void setListener(OnKeyboardAppearedListener listener) {
		this.listener = listener;
	}

	//Viewのサイズが変化した時に呼ばれるメソッド
	@Override
	protected void onSizeChanged(int w, int h, int oldw, int oldh) {
		super.onSizeChanged(w,h,oldw,oldh);

		//キーボード出現時（Viewのサイズが小さくなった場合）のみ
		if(h < oldh){
			//インターフェースを実装したリスナー（Activity、Fragment）のメソッドを呼ぶ
			listener.onKeyboardAppeared(true);			
		}		
	}
}

```

##任意の場所にスクロールさせる

ActivityやFragmentでOnKeyboardAppearedListenerをimplementsし、onKeyboardAppearedメソッドをオーバーライドすれば、キーボード出現（Viewのサイズ変更）をハンドリングできます。

スクロールにはListView#smoothScrollToPositionを使っています。
Viewのサイズ変更が完了しきってないと上手く動作しないので、Handler#postDelayedを使ってタイミングを遅らせています。（もっと良い方法があれば是非。。）
なお、スクロールアニメーションが要らない場合は、ListView#setSelectionでOKです。

smoothScrollToPositionの引数にはposition（アイテム番号）を指定するので、ここではアイテム総数から１を引いた番号、つまり一番最後のアイテムまでスクロールさせています。


```java:main.fragment

public class MainFragment extends Fragment implements OnKeyboardAppearedListener{

	private CustomListView listView;

	@Override
	public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {

		View view = inflater.inflate(R.layout.fragment_main, container, false);

		//CustomListView
		listView = (CustomListView) view.findViewById(R.id.listView);
		//リスナー登録
		listView.setListener(this);

		//ダミーのitemをCustomListViewにセット
		String[] memnuStrings = { "item0","item1","item2","item3","item4","item5","item6","item7","item8","item9","item10","item11","item12", "item13","item14","item15","item16","item17","item18","item19","item20"};
		ArrayAdapter<String> adapter = new ArrayAdapter<String>(getActivity(), android.R.layout.simple_list_item_1, memnuStrings);
		listView.setAdapter(adapter);

		return view;
	}

	@Override
	public void onKeyboardAppeared(boolean isChange) {

		//ListView生成済、且つサイズ変更した（キーボードが出現した）場合
		if(isChange){

			//リストアイテムの総数-1（0番目から始まって最後のアイテム）にスクロールさせる			
			Handler handler = new Handler();
			Runnable runnable = new Runnable() {
				@Override
				public void run() {
					//リストアイテムの総数-1（0番目から始まって最後のアイテム）にフォーカスさせる
					listView.smoothScrollToPosition(listView.getCount()-1);
				}
			};
			handler.postDelayed(runnable, 500);

			//スクロールアニメーションが要らない場合はこれでOK
			//listView.setSelection(listView.getCount()-1);
		}
	}	
}

```

####レイアウト定義

ちなみにCustomListViewはレイアウトのxmlファイルで組み込んでます。

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

    <!-- カスタムリストビュー -->
    <com.example.scrollsample.CustomListView
        android:id="@+id/listView"
        android:layout_width="match_parent"
        android:layout_height="0dp"
        android:layout_weight="1"
        android:scrollingCache="false" />

    <LinearLayout
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:background="#CCC"
        android:orientation="horizontal"
        android:padding="6dp" >

        <EditText
            android:layout_width="0dp"
            android:layout_height="wrap_content"
            android:layout_weight="1"
            android:background="#FFF"
            android:focusableInTouchMode="true"
            android:inputType="textMultiLine"
            android:maxLength="200"
            android:padding="6dp"
            android:textSize="18sp" />

        <Button
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_marginLeft="6dp"
            android:text="Button"
            android:textSize="12sp" />
    </LinearLayout>

</LinearLayout>
```

##おわりに

正直使いどころが微妙ですが、チャットUIや入力フォームがある設定画面をListViewで作りたい場合とかにつかえる、、、かも？