Skip to content

Instantly share code, notes, and snippets.

@vganin
Created October 17, 2015 18:57
Show Gist options
  • Star 12 You must be signed in to star a gist
  • Fork 7 You must be signed in to fork a gist
  • Save vganin/8930b41f55820ec49e4d to your computer and use it in GitHub Desktop.
Save vganin/8930b41f55820ec49e4d to your computer and use it in GitHub Desktop.
Workaround for bug with RecycleView focus scrolling when navigating with d-pad (http://stackoverflow.com/questions/31596801/recyclerview-focus-scrolling)
import android.content.Context;
import android.support.v7.widget.RecyclerView;
import android.util.AttributeSet;
import android.view.View;
/**
* {@link GridLayoutManager} extension which introduces workaround for focus finding bug when
* navigating with dpad.
*
* @see <a href="http://stackoverflow.com/questions/31596801/recyclerview-focus-scrolling">http://stackoverflow.com/questions/31596801/recyclerview-focus-scrolling</a>
*/
public class GridLayoutManager extends android.support.v7.widget.GridLayoutManager {
public GridLayoutManager(Context context, AttributeSet attrs, int defStyleAttr,
int defStyleRes) {
super(context, attrs, defStyleAttr, defStyleRes);
}
public GridLayoutManager(Context context, int spanCount) {
super(context, spanCount);
}
public GridLayoutManager(Context context, int spanCount, int orientation,
boolean reverseLayout) {
super(context, spanCount, orientation, reverseLayout);
}
@Override
public View onFocusSearchFailed(View focused, int focusDirection,
RecyclerView.Recycler recycler, RecyclerView.State state) {
// Need to be called in order to layout new row/column
View nextFocus = super.onFocusSearchFailed(focused, focusDirection, recycler, state);
if (nextFocus == null) {
return null;
}
int fromPos = getPosition(focused);
int nextPos = getNextViewPos(fromPos, focusDirection);
return findViewByPosition(nextPos);
}
/**
* Manually detect next view to focus.
*
* @param fromPos from what position start to seek.
* @param direction in what direction start to seek. Your regular {@code View.FOCUS_*}.
* @return adapter position of next view to focus. May be equal to {@code fromPos}.
*/
protected int getNextViewPos(int fromPos, int direction) {
int offset = calcOffsetToNextView(direction);
if (hitBorder(fromPos, offset)) {
return fromPos;
}
return fromPos + offset;
}
/**
* Calculates position offset.
*
* @param direction regular {@code View.FOCUS_*}.
* @return position offset according to {@code direction}.
*/
protected int calcOffsetToNextView(int direction) {
int spanCount = getSpanCount();
int orientation = getOrientation();
if (orientation == VERTICAL) {
switch (direction) {
case View.FOCUS_DOWN:
return spanCount;
case View.FOCUS_UP:
return -spanCount;
case View.FOCUS_RIGHT:
return 1;
case View.FOCUS_LEFT:
return -1;
}
} else if (orientation == HORIZONTAL) {
switch (direction) {
case View.FOCUS_DOWN:
return 1;
case View.FOCUS_UP:
return -1;
case View.FOCUS_RIGHT:
return spanCount;
case View.FOCUS_LEFT:
return -spanCount;
}
}
return 0;
}
/**
* Checks if we hit borders.
*
* @param from from what position.
* @param offset offset to new position.
* @return {@code true} if we hit border.
*/
private boolean hitBorder(int from, int offset) {
int spanCount = getSpanCount();
if (Math.abs(offset) == 1) {
int spanIndex = from % spanCount;
int newSpanIndex = spanIndex + offset;
return newSpanIndex < 0 || newSpanIndex >= spanCount;
} else {
int newPos = from + offset;
return newPos < 0 && newPos >= spanCount;
}
}
}
@oleynikd
Copy link

Thank you for sharing this!

@cgtarmenta
Copy link

Hello, thanks for sharing, i'm trying to implement your workaround, but i can't getting it to work.
The line
"int fromPos = getPosition(focused);"

trows an Exception, saying that can not cast (any layout params here) to recyclerview.layoutparams.

:/ i've tried to use the layout params from the viewitem, but they doesn't have "getViewLayoutPosition()" function :(

what could I do?

@ederdoski
Copy link

hi have a error in Line "int fromPos = getPosition(focused);" , have update form this issue?

@mEngWork
Copy link

hi have a error in Line "int fromPos = getPosition(focused);" , have update form this issue?

use this : getFocusedChild
int fromPos = getPosition(getFocusedChild());

@Josephaguele
Copy link

i tried this and t did not work for me. It only shifted it a little by one item.

@imrankst1221
Copy link

imrankst1221 commented Oct 23, 2022

For AndroidX RecyclerView you can use this code:

import android.content.Context;
import android.util.AttributeSet;
import android.view.View;

import androidx.recyclerview.widget.GridLayoutManager;
import androidx.recyclerview.widget.RecyclerView;

public class CustomGridLayoutManager extends GridLayoutManager {

    public CustomGridLayoutManager(Context context, AttributeSet attrs, int defStyleAttr,
                             int defStyleRes) {
        super(context, attrs, defStyleAttr, defStyleRes);
    }

    public CustomGridLayoutManager(Context context, int spanCount) {
        super(context, spanCount);
    }

    public CustomGridLayoutManager(Context context, int spanCount, int orientation,
                             boolean reverseLayout) {
        super(context, spanCount, orientation, reverseLayout);
    }

    @Override
    public View onFocusSearchFailed(View focused, int focusDirection,
                                    RecyclerView.Recycler recycler, RecyclerView.State state) {
        // Need to be called in order to layout new row/column
        View nextFocus = super.onFocusSearchFailed(focused, focusDirection, recycler, state);

        if (nextFocus == null) {
            return null;
        }

        int fromPos = getPosition(getFocusedChild());
        int nextPos = getNextViewPos(fromPos, focusDirection);

        return findViewByPosition(nextPos);
    }

    /**
     * Manually detect next view to focus.
     *
     * @param fromPos from what position start to seek.
     * @param direction in what direction start to seek. Your regular {@code View.FOCUS_*}.
     * @return adapter position of next view to focus. May be equal to {@code fromPos}.
     */
    protected int getNextViewPos(int fromPos, int direction) {
        int offset = calcOffsetToNextView(direction);

        if (hitBorder(fromPos, offset)) {
            return fromPos;
        }

        return fromPos + offset;
    }

    /**
     * Calculates position offset.
     *
     * @param direction regular {@code View.FOCUS_*}.
     * @return position offset according to {@code direction}.
     */
    protected int calcOffsetToNextView(int direction) {
        int spanCount = getSpanCount();
        int orientation = getOrientation();

        if (orientation == VERTICAL) {
            switch (direction) {
                case View.FOCUS_DOWN:
                    return spanCount;
                case View.FOCUS_UP:
                    return -spanCount;
                case View.FOCUS_RIGHT:
                    return 1;
                case View.FOCUS_LEFT:
                    return -1;
            }
        } else if (orientation == HORIZONTAL) {
            switch (direction) {
                case View.FOCUS_DOWN:
                    return 1;
                case View.FOCUS_UP:
                    return -1;
                case View.FOCUS_RIGHT:
                    return spanCount;
                case View.FOCUS_LEFT:
                    return -spanCount;
            }
        }

        return 0;
    }

    /**
     * Checks if we hit borders.
     *
     * @param from from what position.
     * @param offset offset to new position.
     * @return {@code true} if we hit border.
     */
    private boolean hitBorder(int from, int offset) {
        int spanCount = getSpanCount();

        if (Math.abs(offset) == 1) {
            int spanIndex = from % spanCount;
            int newSpanIndex = spanIndex + offset;
            return newSpanIndex < 0 || newSpanIndex >= spanCount;
        } else {
            int newPos = from + offset;
            return newPos < 0 && newPos >= spanCount;
        }
    }
}

@sumanabhi
Copy link

The issue is still happening when you going down fastly. and also sometime 2 out of 10 it's loosing the focus.

Is there any updated code there?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment