为 RecyclerView 添加适当的键盘支持
Adding proper keyboard support to RecyclerView
在我之前的一篇 , I asked (and answered by myself following this 博客中 post) 如何正确处理 RecyclerView
上的按键输入。
现在我意识到,如果我按住箭头键,比方说向下键,向下滚动就会停止,RecyclerView
会失去焦点,可能是因为滚动速度比所有子代快View
s.
是否有任何解决方法或更好的做法来正确处理 RecyclerView
上的硬件键盘输入?
更新:
我发布了一个基本示例 here,它现在可以完美运行,不再有焦点丢失。
我设法实现了一个抽象的适配器class能够在不丢失项目焦点的情况下跟踪所选项目,可以找到示例项目here,适配器的具体实现class 如下:
import android.content.Context;
import android.support.v7.widget.RecyclerView;
import android.view.KeyEvent;
import android.view.View;
/**
* Created by vektor on 31/05/16.
*/
public abstract class InputTrackingRecyclerViewAdapter<VH extends RecyclerView.ViewHolder> extends RecyclerView.Adapter<VH>{
private Context mContext;
private int mSelectedItem = 0;
private RecyclerView mRecyclerView;
public InputTrackingRecyclerViewAdapter(Context context){
mContext = context;
}
@Override
public void onAttachedToRecyclerView(final RecyclerView recyclerView) {
super.onAttachedToRecyclerView(recyclerView);
mRecyclerView = recyclerView;
// Handle key up and key down and attempt to move selection
recyclerView.setOnKeyListener(new View.OnKeyListener() {
@Override
public boolean onKey(View v, int keyCode, KeyEvent event) {
RecyclerView.LayoutManager lm = recyclerView.getLayoutManager();
// Return false if scrolled to the bounds and allow focus to move off the list
if (event.getAction() == KeyEvent.ACTION_DOWN) {
if (isConfirmButton(event)) {
if ((event.getFlags() & KeyEvent.FLAG_LONG_PRESS) == KeyEvent.FLAG_LONG_PRESS) {
mRecyclerView.findViewHolderForAdapterPosition(mSelectedItem).itemView.performLongClick();
} else {
event.startTracking();
}
return true;
}
else {
if (keyCode == KeyEvent.KEYCODE_DPAD_DOWN) {
return tryMoveSelection(lm, 1);
} else if (keyCode == KeyEvent.KEYCODE_DPAD_UP) {
return tryMoveSelection(lm, -1);
}
}
}
else if(event.getAction() == KeyEvent.ACTION_UP && isConfirmButton(event)
&& ((event.getFlags() & KeyEvent.FLAG_LONG_PRESS) != KeyEvent.FLAG_LONG_PRESS)){
mRecyclerView.findViewHolderForAdapterPosition(mSelectedItem).itemView.performClick();
return true;
}
return false;
}
});
}
private boolean tryMoveSelection(RecyclerView.LayoutManager lm, int direction) {
int nextSelectItem = mSelectedItem + direction;
// If still within valid bounds, move the selection, notify to redraw, and scroll
if (nextSelectItem >= 0 && nextSelectItem < getItemCount()) {
notifyItemChanged(mSelectedItem);
mSelectedItem = nextSelectItem;
notifyItemChanged(mSelectedItem);
//lm.scrollToPosition(mSelectedItem);
mRecyclerView.smoothScrollToPosition(mSelectedItem);
return true;
}
return false;
}
public Context getContext(){ return mContext; }
public int getSelectedItem() { return mSelectedItem; }
public void setSelectedItem(int selectedItem) { mSelectedItem = selectedItem; }
public RecyclerView getRecyclerView() { return mRecyclerView; }
@Override
public void onBindViewHolder(VH holder, int position) {
onBindViewHolder(holder, position);
}
public static boolean isConfirmButton(KeyEvent event){
switch (event.getKeyCode()){
case KeyEvent.KEYCODE_ENTER:
case KeyEvent.KEYCODE_DPAD_CENTER:
case KeyEvent.KEYCODE_BUTTON_A:
return true;
default:
return false;
}
}
}
在我之前的一篇 RecyclerView
上的按键输入。
现在我意识到,如果我按住箭头键,比方说向下键,向下滚动就会停止,RecyclerView
会失去焦点,可能是因为滚动速度比所有子代快View
s.
是否有任何解决方法或更好的做法来正确处理 RecyclerView
上的硬件键盘输入?
更新:
我发布了一个基本示例 here,它现在可以完美运行,不再有焦点丢失。
我设法实现了一个抽象的适配器class能够在不丢失项目焦点的情况下跟踪所选项目,可以找到示例项目here,适配器的具体实现class 如下:
import android.content.Context;
import android.support.v7.widget.RecyclerView;
import android.view.KeyEvent;
import android.view.View;
/**
* Created by vektor on 31/05/16.
*/
public abstract class InputTrackingRecyclerViewAdapter<VH extends RecyclerView.ViewHolder> extends RecyclerView.Adapter<VH>{
private Context mContext;
private int mSelectedItem = 0;
private RecyclerView mRecyclerView;
public InputTrackingRecyclerViewAdapter(Context context){
mContext = context;
}
@Override
public void onAttachedToRecyclerView(final RecyclerView recyclerView) {
super.onAttachedToRecyclerView(recyclerView);
mRecyclerView = recyclerView;
// Handle key up and key down and attempt to move selection
recyclerView.setOnKeyListener(new View.OnKeyListener() {
@Override
public boolean onKey(View v, int keyCode, KeyEvent event) {
RecyclerView.LayoutManager lm = recyclerView.getLayoutManager();
// Return false if scrolled to the bounds and allow focus to move off the list
if (event.getAction() == KeyEvent.ACTION_DOWN) {
if (isConfirmButton(event)) {
if ((event.getFlags() & KeyEvent.FLAG_LONG_PRESS) == KeyEvent.FLAG_LONG_PRESS) {
mRecyclerView.findViewHolderForAdapterPosition(mSelectedItem).itemView.performLongClick();
} else {
event.startTracking();
}
return true;
}
else {
if (keyCode == KeyEvent.KEYCODE_DPAD_DOWN) {
return tryMoveSelection(lm, 1);
} else if (keyCode == KeyEvent.KEYCODE_DPAD_UP) {
return tryMoveSelection(lm, -1);
}
}
}
else if(event.getAction() == KeyEvent.ACTION_UP && isConfirmButton(event)
&& ((event.getFlags() & KeyEvent.FLAG_LONG_PRESS) != KeyEvent.FLAG_LONG_PRESS)){
mRecyclerView.findViewHolderForAdapterPosition(mSelectedItem).itemView.performClick();
return true;
}
return false;
}
});
}
private boolean tryMoveSelection(RecyclerView.LayoutManager lm, int direction) {
int nextSelectItem = mSelectedItem + direction;
// If still within valid bounds, move the selection, notify to redraw, and scroll
if (nextSelectItem >= 0 && nextSelectItem < getItemCount()) {
notifyItemChanged(mSelectedItem);
mSelectedItem = nextSelectItem;
notifyItemChanged(mSelectedItem);
//lm.scrollToPosition(mSelectedItem);
mRecyclerView.smoothScrollToPosition(mSelectedItem);
return true;
}
return false;
}
public Context getContext(){ return mContext; }
public int getSelectedItem() { return mSelectedItem; }
public void setSelectedItem(int selectedItem) { mSelectedItem = selectedItem; }
public RecyclerView getRecyclerView() { return mRecyclerView; }
@Override
public void onBindViewHolder(VH holder, int position) {
onBindViewHolder(holder, position);
}
public static boolean isConfirmButton(KeyEvent event){
switch (event.getKeyCode()){
case KeyEvent.KEYCODE_ENTER:
case KeyEvent.KEYCODE_DPAD_CENTER:
case KeyEvent.KEYCODE_BUTTON_A:
return true;
default:
return false;
}
}
}