Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Adding proper keyboard support to RecyclerView

In one of my previous questions, I asked (and answered by myself following this blog post) how to properly handle key input on a RecyclerView.

Now I realized that if I keep an arrow key pressed, let's say down key, the scrolling downwards stops and the RecyclerView loses its focus, probably because the scrolling is faster than the generation of all the children Views.

Is there any workaround or better practice to properly handle hardware keyboard inputs on a RecyclerView?

Update:

I published a basic example here, it works flawlessly now, no more focus losses.

like image 525
Vektor88 Avatar asked May 26 '16 12:05

Vektor88


People also ask

What is ViewHolder in RecyclerView Android?

A ViewHolder describes an item view and metadata about its place within the RecyclerView. Adapter implementations should subclass ViewHolder and add fields for caching potentially expensive findViewById results. While LayoutParams belong to the LayoutManager , ViewHolders belong to the adapter.

How can the keyboard be used to navigate your device's features?

Handle directional navigation Users can also navigate your app using the arrow keys on a keyboard (the behavior is the same as when navigating with a D-pad or trackball). The system provides a best-guess as to which view should be given focus in a given direction based on the layout of the views on screen.


1 Answers

I managed to implement an abstract Adapter class capable of keeping track of the selected item without losing item focus, a sample project can be found here, the specific implementation of the adapter class is below:

import android.content.Context;
import android.support.v7.widget.RecyclerView;
import android.view.KeyEvent;
import android.view.View;


/**
 * Created by vektor on 31/05/16.
 */
public abstract class InputTrackingRecyclerViewAdapter<VH extends RecyclerView.ViewHolder> extends RecyclerView.Adapter<VH>{

    private Context mContext;
    private int mSelectedItem = 0;
    private RecyclerView mRecyclerView;

    public InputTrackingRecyclerViewAdapter(Context context){
        mContext = context;
    }

    @Override
    public void onAttachedToRecyclerView(final RecyclerView recyclerView) {
        super.onAttachedToRecyclerView(recyclerView);

        mRecyclerView = recyclerView;
        // Handle key up and key down and attempt to move selection
        recyclerView.setOnKeyListener(new View.OnKeyListener() {
            @Override
            public boolean onKey(View v, int keyCode, KeyEvent event) {
                RecyclerView.LayoutManager lm = recyclerView.getLayoutManager();

                // Return false if scrolled to the bounds and allow focus to move off the list
                if (event.getAction() == KeyEvent.ACTION_DOWN) {
                    if (isConfirmButton(event)) {
                        if ((event.getFlags() & KeyEvent.FLAG_LONG_PRESS) == KeyEvent.FLAG_LONG_PRESS) {
                            mRecyclerView.findViewHolderForAdapterPosition(mSelectedItem).itemView.performLongClick();
                        } else {
                            event.startTracking();
                        }
                        return true;
                    }
                    else {
                        if (keyCode == KeyEvent.KEYCODE_DPAD_DOWN) {
                            return tryMoveSelection(lm, 1);
                        } else if (keyCode == KeyEvent.KEYCODE_DPAD_UP) {
                            return tryMoveSelection(lm, -1);
                        }
                    }
                }
                else if(event.getAction() == KeyEvent.ACTION_UP && isConfirmButton(event)
                        && ((event.getFlags() & KeyEvent.FLAG_LONG_PRESS) != KeyEvent.FLAG_LONG_PRESS)){
                    mRecyclerView.findViewHolderForAdapterPosition(mSelectedItem).itemView.performClick();
                    return true;
                }
                return false;
            }
        });
    }

    private boolean tryMoveSelection(RecyclerView.LayoutManager lm, int direction) {
        int nextSelectItem = mSelectedItem + direction;

        // If still within valid bounds, move the selection, notify to redraw, and scroll
        if (nextSelectItem >= 0 && nextSelectItem < getItemCount()) {
            notifyItemChanged(mSelectedItem);
            mSelectedItem = nextSelectItem;
            notifyItemChanged(mSelectedItem);
            //lm.scrollToPosition(mSelectedItem);
            mRecyclerView.smoothScrollToPosition(mSelectedItem);
            return true;
        }

        return false;
    }

    public Context getContext(){ return mContext; }

    public int getSelectedItem() { return mSelectedItem; }
    public void setSelectedItem(int selectedItem) { mSelectedItem = selectedItem; }

    public RecyclerView getRecyclerView() { return mRecyclerView; }


    @Override
    public void onBindViewHolder(VH holder, int position) {
        onBindViewHolder(holder, position);
    }

    public static boolean isConfirmButton(KeyEvent event){
        switch (event.getKeyCode()){
            case KeyEvent.KEYCODE_ENTER:
            case KeyEvent.KEYCODE_DPAD_CENTER:
            case KeyEvent.KEYCODE_BUTTON_A:
                return true;
            default:
                return false;
        }
    }


}
like image 90
Vektor88 Avatar answered Oct 12 '22 14:10

Vektor88