dzboot02
dzboot02

Reputation: 2979

Prevent Recyclerview from losing focus

I am developping this android TV app which has one activity containing some buttons. After clicking a button, a RecyclerView (in the same activity) gets populated and automatically focused.

The problem is when navigating with DPAD toward the direction of the buttons, the RecyclerView loses focus to the buttons. I want to prevent this behavior, i.e. prevent the RecyclerView from losing focus to anything else on the activity. Later, the only way to select another button is by clicking the back button, this I can do, no problem.

Here is what I have tried, and did not work:

<androidx.recyclerview.widget.RecyclerView
        android:id="@+id/categories_rv"
        android:layout_width="@dimen/categories_list_width"
        android:layout_height="wrap_content"
        android:focusable="true"
        android:nextFocusForward="@id/categories_rv"
        android:nextFocusUp="@id/categories_rv"
        android:nextFocusRight="@id/categories_rv"
        android:nextFocusLeft="@id/categories_rv"
        android:nextFocusDown="@id/categories_rv"
        android:focusableInTouchMode="true" />

And this:

 binding.categoriesRv.setOnFocusChangeListener { v, hasFocus ->
     if (!hasFocus)
        v.requestFocus()
  }

In the second way, the FocusChangeListener never gets called unless I use android:descendantFocusability="blocksDescendants" in the RecyclerView, but this prevents the items of the RecyclerView of being focused.

Upvotes: 3

Views: 9361

Answers (4)

ReDLaNN
ReDLaNN

Reputation: 524

First of all i worked for 3 years on AndroidTv fighting against the focus management, it is actually pretty simple, the problem is...there is no documentation!

On Android-TV the right way to manage the Dpad navigation is actually to use the focusSearch method. What does this do?

DPad navigation actually means the focus changes between some Views. So who should decide where the focus should go when you press Dpad-Right? The ViewGroup (the parent) containing the Views does!

A little background: when a view does not know who should get the focus, it asks the ViewGroup to take the decision. So in most cases when you push DpadRight while in focus on a View, focusSearch is called on its container with direction = FOCUS_RIGHT (FOCUS_END) and so on.

The solution for you is then pretty simple. You have 2 ways:

  1. Put the Recyclerview inside a BrowseFrameLayout then you can do the following
BrowseFrameLayout browseFrameLayout = view.findViewById(R.id.recyclerview_container);
RecyclerView recyclerView = view.findViewById(R.id.categories_rv);
theBrowseFrameLayout.setOnFocusSearchListener(new BrowseFrameLayout.OnFocusSearchListener() {
    @Override
    public View onFocusSearch(View focused, int direction) {
        if (recyclerView.hasFocus())
            return focused; // keep focus on recyclerview! DO NOT return recyclerview, but focused, which is a child of the recyclerview
        else
            return null; // someone else will find the next focus
    }
});
  1. You can create a custom view extending any container you are using for your recyclerview and do the same, here is a little more complex example that we use with an UI having a top menu and various fragments under it : https://gist.github.com/RedLann/bfaffc06eb0f571234c6e46b697d633b

NOTE: Usually in xml we use on recyclerview android:descendantFocusability="afterDescendants"

NOTE: Just so you know, when a View gets focused for the first time, there is a method of that view that is called, which is:

protected boolean onRequestFocusInDescendants(int direction, Rect previouslyFocusedRect)

NOTE: If you use this approach, the focus management becomes really easy, each Parent view (ViewGroup/Container) will manage the focus for its direct children, when it doesn't know what to do it can delegate to its own parent viewgroup returning super.focusSearch

Upvotes: 24

Master
Master

Reputation: 683

How I solved the problem:

private int focusedPosition;

@Override
public void onBindViewHolder(@NonNull RecyclerView.ViewHolder holder, int position) {
    final ItemViewHolder viewHolder = (ItemViewHolder) holder;
    viewHolder.llContainer.setOnClickListener(v -> {
         сlickCallback.itemClick(position);
         focusedPosition = position;
    });
     
    if (focusedPosition == position) viewHolder.llContainer.requestFocus();
    ...
}

Upvotes: 1

Andrain
Andrain

Reputation: 910

You can achieve this by implementing key listeners on your controls and recyclerview and manage this thing manually.

Add the below method in your adapter class and define each step which you want to perform on every click. It is working in my app I hope it helps you too.

@Override
public void onAttachedToRecyclerView(final RecyclerView recyclerView) {
    super.onAttachedToRecyclerView(recyclerView);
    recyclerView.setOnKeyListener(new View.OnKeyListener() {
        @Override
        public boolean onKey(View v, int keyCode, KeyEvent event) {

            LinearLayoutManager lm = ((LinearLayoutManager) recyclerView.getLayoutManager());

            if (event.getAction() == KeyEvent.ACTION_DOWN) {
                if (keyCode == KeyEvent.KEYCODE_DPAD_DOWN) {
                    //return false or true; // whatever your case is
                } else if (keyCode == KeyEvent.KEYCODE_DPAD_UP) { 
                  return false;

                } else if (keyCode == KeyEvent.KEYCODE_DPAD_RIGHT) {

                } else if (keyCode == KeyEvent.KEYCODE_DPAD_LEFT) {

                } else if (keyCode == KeyEvent.KEYCODE_ENTER) {

                } else if (keyCode == KeyEvent.KEYCODE_DPAD_CENTER) {
                }
            }
            return false;
        }
    });


    recyclerView.setOnFocusChangeListener(new View.OnFocusChangeListener() {
        @Override
        public void onFocusChange(View v, boolean hasFocus) {
            if (hasFocus) {


            } else if (!hasFocus) {

            }
        }
    });
}

Upvotes: 0

dgncn
dgncn

Reputation: 25

No matter what you do if you using recyclerview in android tv app it will lose focus. Only solution is using leanback fragments.

Upvotes: 0

Related Questions