-
-
Save vganin/8930b41f55820ec49e4d to your computer and use it in GitHub Desktop.
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; | |
} | |
} | |
} |
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?
hi have a error in Line "int fromPos = getPosition(focused);" , have update form this issue?
hi have a error in Line "int fromPos = getPosition(focused);" , have update form this issue?
use this : getFocusedChild
int fromPos = getPosition(getFocusedChild());
i tried this and t did not work for me. It only shifted it a little by one item.
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;
}
}
}
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?
Thank you for sharing this!