Created
October 17, 2015 18:57
-
-
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)
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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; | |
} | |
} | |
} |
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?
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
i tried this and t did not work for me. It only shifted it a little by one item.