Reputation: 2979
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
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:
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
}
});
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
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
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
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