在TV上,用RecyclerView显示一个列表,飞鼠遥控左右遥控获得Item焦点,到最后一个进行右键换行,是不能做到的,因此需要监听key事件处理换行。
效果图如下
代码实现
Item.xml布局
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:padding="10dp"
android:clickable="true"
android:focusable="true"
android:gravity="center"
android:focusableInTouchMode="true"
android:background="@drawable/focusable_view_bg"
android:orientation="vertical">
<ImageView
android:id="@+id/img"
android:layout_width="100dp"
android:layout_height="100dp"
android:src="@drawable/girl1"
android:scaleType="fitXY" />
<TextView
android:id="@+id/title"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Test1" />
</LinearLayout>
focusable_view_bg.xml 获得焦点和悬浮
在drawable创建focusable_view_bg.xml
<selector xmlns:android="http://schemas.android.com/apk/res/android">
<!-- 悬浮 -->
<item android:state_hovered="true">
<shape>
<corners android:radius="15dp" />
<solid android:color="#66000000" />
<stroke android:width="2dp" android:color="#fff000" />
</shape>
</item>
<!-- 获得焦点 -->
<item android:state_focused="true">
<shape>
<corners android:radius="15dp" />
<stroke android:width="2dp" android:color="#fff000" />
<solid android:color="#66000000" />
</shape>
</item>
</selector>
activity_main.xml布局
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".MainActivity">
<Button
android:id="@+id/btn_move_left"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="左移动"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
<Button
android:id="@+id/btn_move_right"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="右移动"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
<Button
android:id="@+id/btn_enter"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="点击"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintTop_toTopOf="parent" />
<androidx.recyclerview.widget.RecyclerView
android:id="@+id/recycler_list"
android:layout_width="0dp"
android:layout_height="0dp"
android:focusable="true"
android:clickable="true"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/btn_move_right" />
</androidx.constraintlayout.widget.ConstraintLayout>
Adapter类
package com.dfg.recyclerviewfocus;
import android.content.Context;
import android.graphics.Bitmap;
import android.view.KeyEvent;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.ImageView;
import android.widget.TextView;
import androidx.annotation.NonNull;
import androidx.recyclerview.widget.RecyclerView;
import java.util.ArrayList;
import java.util.Map;
public class MyAdapter extends RecyclerView.Adapter<RecyclerView.ViewHolder> {
private Context context;
private ArrayList<Map<String, Object>> list;
private OnItemClickListener itemClickListener;
private OnIconKeyListener iconKeyListener;
public void setOnItemClickListener(OnItemClickListener itemClickListener) {
this.itemClickListener = itemClickListener;
}
public void setOnIconKeyListener(OnIconKeyListener iconKeyListener) {
this.iconKeyListener = iconKeyListener;
}
public MyAdapter(Context context, ArrayList<Map<String, Object>> list) {
this.context = context;
this.list = list;
}
static class ViewHolderItem extends RecyclerView.ViewHolder {
ImageView imgAppPic;//app图片
TextView tvAppName;// app 名字
public ViewHolderItem(View view) {
super(view);
imgAppPic = view.findViewById(R.id.img);
tvAppName = view.findViewById(R.id.title);
}
}
@NonNull
@Override
public RecyclerView.ViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
View view = LayoutInflater.from(parent.getContext()).inflate(R.layout.item, parent, false);
ViewHolderItem viewHolderItem = new ViewHolderItem(view);
return viewHolderItem;
}
@Override
public void onBindViewHolder(@NonNull RecyclerView.ViewHolder viewHolder, int position) {
ViewHolderItem holder = (ViewHolderItem) viewHolder;
Map<String, Object> item = list.get(position);
holder.imgAppPic.setImageBitmap((Bitmap) item.get("icon"));
holder.tvAppName.setText(item.get("title").toString());
holder.itemView.setOnClickListener(v -> {
if (itemClickListener != null) {
itemClickListener.itemClick(v, holder.getAdapterPosition());
}
});
holder.itemView.setOnKeyListener(new View.OnKeyListener() {
@Override
public boolean onKey(View v, int keyCode, KeyEvent event) {
if(iconKeyListener!=null) {
return iconKeyListener.onKey(v,keyCode,event,holder.getAdapterPosition());
}
return false;
}
});
}
@Override
public int getItemCount() {
return list.size();
}
// 点击 Item 回调
interface OnItemClickListener {
void itemClick(View view, int position);
}
// 回调Key
interface OnIconKeyListener{
boolean onKey(View v, int keyCode, KeyEvent event,int position);
}
}
MainActivity
public class MainActivity extends AppCompatActivity {
private String TAG = "MainActivity";
private Button btnMoveRight;
private Button btnMoveLeft;
private Button btnEnter;
private RecyclerView recyclerView;
private MyAdapter myAdapter;
private ArrayList<Map<String, Object>> list = new ArrayList<>();
// 列数,网格布局中每行4个Item
private int numColumns = 4;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
init();
setData();
click();
}
public void init() {
btnMoveRight = findViewById(R.id.btn_move_right);
btnMoveLeft = findViewById(R.id.btn_move_left);
btnEnter = findViewById(R.id.btn_enter);
recyclerView = findViewById(R.id.recycler_list);
}
/**
* 设置数据源并初始化RecyclerView
*/
public void setData() {
for (int i = 0; i < 30; i++) {
Map map = new HashMap();
map.put("icon", BitmapFactory.decodeResource(getResources(), R.drawable.girl1));
map.put("title", "test" + i);
list.add(map);
}
myAdapter = new MyAdapter(getApplicationContext(), list);
GridLayoutManager gridLayoutManager = new GridLayoutManager(this, numColumns);
recyclerView.setLayoutManager(gridLayoutManager);
recyclerView.setAdapter(myAdapter);
}
public void click() {
// 右移按钮点击事件
btnMoveRight.setOnClickListener(v -> {
try {
// 查找当前获得焦点的视图
View focusedView = recyclerView.findFocus();
// 如果RecyclerView没有获得焦点
if (focusedView != null) {
// 获取RecyclerView的子类第0个item
int position = recyclerView.getChildAdapterPosition(focusedView);
Log.d(TAG, "当前获得焦点的Item位置: " + position);
Runtime.getRuntime().exec("input keyevent 22");
} else {
Log.d(TAG, "没有任何Item获得焦点");
if (recyclerView.getLayoutManager() != null) {
// 如果没有获得焦点的视图,默认让第一个可见项获得焦点
int firstPosition = ((GridLayoutManager) recyclerView.getLayoutManager()).findFirstVisibleItemPosition();
View positionChild = recyclerView.getLayoutManager().findViewByPosition(firstPosition);
if (positionChild != null) {
positionChild.requestFocus();// 让第一个Item获得焦点
}
}
}
} catch (Exception e) {
throw new RuntimeException(e);
}
});
// 左移按钮点击事件
btnMoveLeft.setOnClickListener(v -> {
try {
// 查找当前获得焦点的视图
View focusedView = recyclerView.findFocus();
// 如果RecyclerView没有获得焦点
if (focusedView != null) {
// 获取RecyclerView的子类第0个item
int position = recyclerView.getChildAdapterPosition(focusedView);
Log.d(TAG, "当前获得焦点的Item位置: " + position);
} else {
Log.d(TAG, "没有任何Item获得焦点");
if (recyclerView.getLayoutManager() != null) {
// 如果没有获得焦点的视图,默认让第一个可见项获得焦点
int firstPosition = ((GridLayoutManager) recyclerView.getLayoutManager()).findFirstVisibleItemPosition();
View positionChild = recyclerView.getLayoutManager().findViewByPosition(firstPosition);
if (positionChild != null) {
positionChild.requestFocus();// 让第一个Item获得焦点
}
}
}
Runtime.getRuntime().exec("input keyevent 21");
} catch (IOException e) {
throw new RuntimeException(e);
}
});
// 确认按钮点击事件
btnEnter.setOnClickListener(v -> {
try {
Runtime.getRuntime().exec("input keyevent 66");
} catch (IOException e) {
throw new RuntimeException(e);
}
});
myAdapter.setOnItemClickListener(new MyAdapter.OnItemClickListener() {
@Override
public void itemClick(View view, int position) {
Toast.makeText(getApplicationContext(), list.get(position).get("title").toString(), Toast.LENGTH_SHORT).show();
}
});
// 设置RecyclerView Item键盘事件监听
myAdapter.setOnIconKeyListener(new MyAdapter.OnIconKeyListener() {
@Override
public boolean onKey(View v, int keyCode, KeyEvent event, int position) {
// 获取按键动作类型
final int action = event.getAction();
// 检查按键是否为按下动作
final boolean handleKeyEvent = (action != KeyEvent.ACTION_UP);
// 标记按键是否被处理
boolean wasHandled = false;
switch (keyCode) {
// 左键按下事件
case KeyEvent.KEYCODE_DPAD_LEFT:
// 不是手指抬起操作
if (handleKeyEvent) {
// 如果当前的 Item 是 LinearLayout
if (v instanceof LinearLayout) {
// 当前当前的 Item父类 是 RecyclerView
if (v.getParent() instanceof RecyclerView) {
// 如果当前项在一列最后一项 或 第0项
if (position % numColumns == 0) {
// position的位置一定要 >= 0,因为这里要进行换行了
if (position - 1 >= 0) {
if (recyclerView.getLayoutManager() != null) {
// 这里进行位置 -1,如果是屏幕看不到上一行,就会为Null
View positionChild = recyclerView.getLayoutManager().findViewByPosition(position - 1);
if (positionChild != null) {
// 将焦点移动到前一个Item
positionChild.requestFocus();
} else {
// 如果当前屏幕看不到上一个Item时,这里就会为 null,然后上滑到前一项。
// 平滑滚动到前一项
recyclerView.smoothScrollToPosition(position - 1);
try {
// 再次执行左键按下
Runtime.getRuntime().exec("input keyevent 21");
} catch (IOException e) {
throw new RuntimeException(e);
}
}
wasHandled = true;
}
}
}
}
}
}
break;
case KeyEvent.KEYCODE_DPAD_RIGHT:
// 不是手指抬起操作
if (handleKeyEvent) {
// 如果当前的 Item 是 LinearLayout
if (v instanceof LinearLayout) {
// 当前当前的 Item父类 是 RecyclerView
if (v.getParent() instanceof RecyclerView) {
// 当前的位置+1 < adapter的item总数
if (position + 1 < myAdapter.getItemCount()) {
// 如果 当前位置+1 % 列数 =0,表示下一个是下一行了
if ((position + 1) % numColumns == 0) {
if (recyclerView.getLayoutManager() != null) {
// 获取下一个item,如果是屏幕看不到下一行,就会为Null
View positionChild = recyclerView.getLayoutManager().findViewByPosition(position + 1);
if (positionChild != null) {
// 将焦点移动到下一个Item
positionChild.requestFocus();
} else {
// 如果当前屏幕看不到下一个Item时,这里就会为 null,然后下滑到前一项。
// 平滑滚动到下一项
recyclerView.smoothScrollToPosition(position + 1);
try {
Runtime.getRuntime().exec("input keyevent 22");
} catch (IOException e) {
throw new RuntimeException(e);
}
}
}
// 返回true,事件自己消费处理了。
wasHandled = true;
}
} else if (position + 1 == myAdapter.getItemCount()) {
wasHandled = true;
}
}
}
}
break;
}
return wasHandled;
}
});
}
}
RecyclerView相关方法
- recyclerView.getLayoutManager().findViewByPosition(positon):获取当前显示的某个位置的子视图。
- recyclerView.getChildAdapterPosition(View):获取某个子视图在适配器中的位置。
- recyclerView.smoothScrollToPosition(positon):平滑滚动
RecyclerView
到指定位置。