RecyclerViewのフォーカス位置をトグルする
Android TVや業務用のシステムにAndroidが利用されるケースが増えてきていることでDPADを利用したアプリケーションの開発が再度行われるようになってきました。
そうした中でRecyclerViewで表示した画面の一番上のアイテムにフォーカスが当たっている状態で上キーを押下した時にリストの一番下にフォーカスを移動したいケースや一番下のアイテムを選択している時に下キーを押下すると一番上にフォーカスを移動したいケースがあります。
そのやり方について説明します。
RecyclerView内のAdapterで表示されるアイテムごとにキーイベントを設定します。
そのキーイベントの設定内容は次のように設定します。
@Override public boolean onKey(View v, int keyCode, KeyEvent event) { int position = mRecyclerView.getChildAdapterPosition(v); int size = mRecyclerAdapter.getItemCount(); switch (event.getAction()) { case KeyEvent.ACTION_DOWN: switch (event.getKeyCode()) { case KeyEvent.KEYCODE_DPAD_UP: if (position == 0) { RecyclerView.ViewHolder holder = mRecyclerView.findViewHolderForAdapterPosition(size - 1); if (holder != null && holder.itemView != null) { holder.itemView.requestFocus(); } else { mRecyclerView.addOnChildAttachStateChangeListener(new RecyclerView.OnChildAttachStateChangeListener() { @Override public void onChildViewAttachedToWindow(View view) { int size = mRecyclerAdapter.getItemCount(); if (mRecyclerView.getChildAdapterPosition(view) == size - 1) { view.requestFocus(); mRecyclerView.removeOnChildAttachStateChangeListener(this); } } @Override public void onChildViewDetachedFromWindow(View view) { } }); mRecyclerView.scrollToPosition(size - 1); return true; } } return false; case KeyEvent.KEYCODE_DPAD_DOWN: if (position == (size -1)) { RecyclerView.ViewHolder holder = mRecyclerView.findViewHolderForAdapterPosition(0); if (holder != null && holder.itemView != null) { holder.itemView.requestFocus(); } else { mRecyclerView.addOnChildAttachStateChangeListener(new RecyclerView.OnChildAttachStateChangeListener() { @Override public void onChildViewAttachedToWindow(View view) { view.requestFocus(); mRecyclerView.removeOnChildAttachStateChangeListener(this); } @Override public void onChildViewDetachedFromWindow(View view) { } }); mRecyclerView.scrollToPosition(0); return true; } } } }
RecyclerViewのDPADのイベントの厄介なところはキーを押し込んだ時と離した時に発生するイベントは別のViewにイベントが発行されます。
そのため1つ目のアイテムにフォカースされている状態で押し込んだ時に1つ目のアイテムにDOWNイベントが発生し、2つ目のアイテムにUPイベントが発生します。
この時にアイテムの1つ目にはUPイベントが発行されません。
さらに長押しすると断続的にUPイベントとDOWNイベントが送られてきます。
そこでUPイベントが発生するタイミングで一番上のアイテムにフォーカスが当たっている時に上キーを離したタイミングで一番下のアイテムにフォーカス移動をさせます。
一番下のアイテムが表示中ならば10行目から12行目までのViewHolderを取り出してholder.itemView.requestFocus();
をするだけでフォーカスが当たるので問題ありません。
困るのはスクロールしないとフォーカスを当てれないケースです。
スクロールする際はscrollToPositionを利用しますがこれをするだけではフォーカスが移動してくれないのでaddOnChildAttachStateChangeListenerのonChildViewAttachedToWindowで一番下のアイテムが表示された時にフォーカスを移動させます。
そのため下記の処理の部分が重要となります。
if (mRecyclerView.getChildAdapterPosition(view) == size - 1) { view.requestFocus(); mRecyclerView.removeOnChildAttachStateChangeListener(this); }
あとはこれと反対のことをやれば一番下のアイテムから一番上のアイテムへ移動することができます。