techium

このブログは何かに追われないと頑張れない人たちが週一更新をノルマに技術情報を発信するブログです。もし何か調査して欲しい内容がありましたら、@kobashinG or @muchiki0226 までいただけますと気が向いたら調査するかもしれません。

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);
                        }

あとはこれと反対のことをやれば一番下のアイテムから一番上のアイテムへ移動することができます。