16

In my fire tv app I'm using a recyclerview with horizontal layout.

Scrolling with dpad works and also items are gaining focus.

But when I hold the button it scrolls very fast because many keydown events are triggered, and items are losing their focus and it's not possible to scroll anymore because another Textview above my recyclerview is gaining the focus.

It looks like a bug. Is there any workaround for this?

Yassin Hajaj
  • 21,337
  • 9
  • 51
  • 89
pawlinsky
  • 420
  • 1
  • 6
  • 18
  • 3
    For those who will face the same problem. I found a workaround for it. I'm not using RecyclerView anymore. I've switched to this HorizontalListView lib: https://github.com/sephiroth74/HorizontalVariableListView, which is sufficient for my needs. – pawlinsky Dec 18 '15 at 10:58

8 Answers8

9

I think the answer by @tmm1 is the best one so far. I have successfully implemented this workaround for the issue of elements loaded in a RecyclerView loosing focus if user uses the DPAD too quickly to scroll.

In my RecyclerView's adapter I use a LayoutInflater to inflate my element views like this

@Override
public ListItemViewHolder onCreateViewHolder(ViewGroup viewGroup, int viewType) {
    View itemView = LayoutInflater.from(viewGroup.getContext()).inflate(R.layout.item_keyboard_key, viewGroup, false);
    return new ListItemViewHolder(itemView);
}

My item_keyboard_key.xml were originally defined like this

<?xml version="1.0" encoding="utf-8"?>
<FrameLayout 
    xmlns:android="http://schemas.android.com/apk/res/android"
    android:id="@+id/item_keyboard_key_layout"
    android:orientation="vertical"
    android:gravity="center"
    android:layout_width="wrap_content"
    android:layout_height="wrap_content">

    <TextView
        android:id="@+id/item_keyboard_key_text"
        android:text="A"/>

</FrameLayout>

Then I created a new custom FrameLayout class (FocusFixFrameLayout) extending FrameLayout and using this as my root layout. My item_keyboard_key.xml then became

<?xml version="1.0" encoding="utf-8"?>
<no.bluebit.views.FocusFixFrameLayout 
    xmlns:android="http://schemas.android.com/apk/res/android"
    android:id="@+id/item_keyboard_key_layout"
    android:orientation="vertical"
    android:gravity="center"
    android:layout_width="wrap_content"
    android:layout_height="wrap_content">

    <TextView
        android:id="@+id/item_keyboard_key_text"
        android:text="A"/>

</no.bluebit.views.FocusFixFrameLayout>

and my FocusFixFrameLayout class looks like this

public class FocusFixFrameLayout extends FrameLayout {

    public FocusFixFrameLayout(@NonNull Context context) {
        super(context);
    }

    public FocusFixFrameLayout(@NonNull Context context, @Nullable AttributeSet attrs) {
        super(context, attrs);
    }

    public FocusFixFrameLayout(@NonNull Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
    }

    public FocusFixFrameLayout(@NonNull Context context, @Nullable AttributeSet attrs, int defStyleAttr, int defStyleRes) {
        super(context, attrs, defStyleAttr, defStyleRes);
}

    @Override
    public void clearFocus() {
        if(this.getParent() != null)
            super.clearFocus();
    }
}
Håvard Bakke
  • 259
  • 1
  • 4
  • 10
  • 1
    Hi, Havard. It is really solving my issue. Very very thanks to you. You are a lifesaver)) – Serg Burlaka Dec 11 '19 at 14:51
  • 2
    This solved the problem on some devices, but on others it persists (e.g. Fire OS 5 devices on API 21) – joe1806772 Oct 20 '20 at 13:47
  • I'm getting "java.lang.NullPointerException: Attempt to invoke virtual method 'boolean android.view.ViewGroup.getTouchscreenBlocksFocus()' on a null object reference" with this. Has anything changed since this answer was posted? – Gustav P Svensson Apr 11 '21 at 10:34
  • 3
    I tried with my ConstraintLayout, but it doesn't work. At some point, the recycler view and the whole screen completely lose focus. – kirkadev Sep 01 '21 at 07:00
  • 1
    Tried for my RelativeLayout and recyclerview started lost focus more often then it was before. – PavelGP Sep 16 '21 at 17:13
  • Did not work at all for me. – Trevor Feb 03 '22 at 06:51
5

I face the same problem, what i do in my project likes below in my Activity (containing RecyclerView):

    private long mLastKeyDownTime = 0;
    @Override
    public boolean onKeyDown(int keyCode, KeyEvent event) {
        long current = System. currentTimeMillis();
        boolean res = false;
        if (current - mLastKeyDownTime < 300 ) {
            res = true;
        } else {
            res = super.onKeyDown(keyCode, event);
            mLastKeyDownTime = current;
        }
        return res;
    }

which avoids the fast scrolling when you hold the button on the dpad, and the focus works fine in my RecyclerView.

Xiaozou
  • 1,655
  • 1
  • 15
  • 29
5

Looks like this is a bug.

See https://stackoverflow.com/a/33190656/332798 and https://issuetracker.google.com/issues/37067220


I was able to work around this bug by neutering clearFocus() on my item views, so detached views losing focus did not move focus outside the RecyclerView:

override fun clearFocus() {
    if (parent != null)
        super.clearFocus()
}
tmm1
  • 2,025
  • 1
  • 20
  • 35
1

I had the same issue when I used notifyItemChanged(position). Solved with sending an empty payload in addition to position like this notifyItemChanged(position, Any())

0

Try to Use notifyItemChanged(position) It's Work from me. I have call adapter inside adapter and my child adapter has multiple edittext which lose focus when i call notifydatasetchange. So i replace with notifyItemChanged(Position).

notifyItemChanged(position);
Nimesh Patel
  • 1,394
  • 13
  • 26
0

Updated Answer to 2023:

Recyclerview loses focus when using the DPAD and its a known bug. A little hack I use is to override onBackPressed and add the following:

@Override
public void onBackPressed() {
    Log.i("MainActivity", "getCurrentFocus(): "+getCurrentFocus());
    }

This hack or line of code lets me know when I lose focus who has the current focus by pressing Back and you will get something with a hint like this:

getCurrentFocus(): androidx.recyclerview.widget.RecyclerView{12c0bcc VFED.VC.. .F...... 0,0-1843,864 #7f0a016a app:id/recycler_view}

With this you will know who is getting focus and you can add an onFocusChange listener to that view or just disable focusability on that view all together. The important thing is you find out who is getting that focus when recyclerview loses it.

Adrian M Cavazos
  • 175
  • 1
  • 13
0

This has been somewhat of an ongoing issue, but just going to share my workaround on Android TV. I have tried wrapping the items in a custom FrameLayout and overriding the clearFocus as many have suggested, but this does not work in all cases especially for larger RecyclerView items.

Focus is lost when scrolling the RecyclerView on the d-pad because it tries to focus on the next item on the list which appears to be off the screen and not yet recycled, so it instead focuses on other existing views on the screen.

So here's my approach:

override fun onInterceptFocusSearch(focused: View, direction: Int): View? {
    val pos = getPosition(focused)
    val count = itemCount

    if (direction == View.FOCUS_DOWN && pos + 1 < count) {
        scrollToPosition(pos + 1)
    }
    return super.onInterceptFocusSearch(focused, direction)
}

I overrided this method in a custom LinearLayoutManager for my vertical RecyclerView to pre-scroll the list to an item next to the currently focused one so that there's always an available next item to focus. It's not the best but it works and prevents focus redirection.

Another option can be to increase the number of visible items as the list is scrolled - I'm not sure exactly on how to achieve this. If anyone's come up with anything better please share. Hopefully this helps someone!

6rchid
  • 1,074
  • 14
  • 19
-1

I have faced the same problem when was working on project for Android TV and leanback library.

The good news is that popular apps(YouTube and others) do not have this issue. I found informative resource. After investigation I made a solution that is convenient to me. The key is to use Presenter instead of Adapter There is the simplest example

//Presenter class
public class ItemViewPresenter extends Presenter {

    @Override
    public final ViewHolder onCreateViewHolder(ViewGroup parent) {

        //I use data binding
        ItemBinding binding = DataBindingUtil.inflate(LayoutInflater.from(parent.getContext()), R.layout.item, parent, false);
        return new MyViewHolder(binding);
    }

    @Override
    public final void onBindViewHolder(ViewHolder viewHolder, Object item) {
        String text = (String) item;
        ((MyViewHolder) viewHolder).bind(text);
    }

    @Override
    public final void onUnbindViewHolder(ViewHolder viewHolder) {
    }

    private class MyViewHolder extends Presenter.ViewHolder {

        private final ItemBinding binding;

        private DiscoveryViewHolder(ItemBinding binding) {
            super(binding.getRoot());
            this.binding = binding;
        }

        private void bind(String text) {

            binding.textView.setText(text);
        }
    }
}

//... 
private void setupRecyclerView() {

    RecyclerView recyclerView = _baseLayout.findViewById(R.id.recyclerView);

    ArrayObjectAdapter arrayObjectAdapter = new ArrayObjectAdapter(new ItemViewPresenter());

    ItemBridgeAdapter bridgeAdapter = new ItemBridgeAdapter(arrayObjectAdapter);

    recyclerView.setAdapter(bridgeAdapter);

    LinearLayoutManager layoutManager = new LinearLayoutManager(_baseLayout.getContext(), LinearLayoutManager.HORIZONTAL, false);
    recyclerView.setLayoutManager(layoutManager);

    List<Strings> textList = new ArrayList(1);
    textList.add("1");

    //add objects
    arrayObjectAdapter.addAll(0, textList);

    //clear objects
    //arrayObjectAdapter.clear();
}
yoAlex5
  • 29,217
  • 8
  • 193
  • 205