背景
今天在学习demo的时候遇到一个问题,当页面中长网格列表右边边沿再往右方向移动的时候,焦点会停留在原来位置上面,这个也是系统默认的行为,不是bug。 我在gimi盒子上面的列表行为是右边的最后一个按右会跳到下一行的第一个item上面。我当时正在摸鱼,突然就想我也要这样做~,别人有的我也要有~~。
先看看默认情况下的焦点行为。
到了行尽头之后,停止了,再按右没反应的,这是正常的。
分析如何换行
- 我怎么知道我现在在哪里呢
- 知道了我在哪里(我到了最右边),然后怎么找到下一行第一个呢
- 我知道了一行的位置了,怎么切换过去?
上面3个问题,就是分析的过程。 我们一个一个来处理。
监听遥控器按键
ViewGroup中可以通过重写dispatchKeyEvent这个方法监听当前vg内部的按键事件。这里我们需要2个条件来辅助 1:遥控器按下了按钮 2:方向是向右?? ok,明确了条件,就是写体力活了(写代码)
@Override
public boolean dispatchKeyEvent(KeyEvent event) {
//拦截右边的按键操作,然后查找出下一个item的位置,主动聚焦到改item上面
if (event.getAction() == KeyEvent.ACTION_DOWN) {
//如果是遥控器按键按下的操作
if (event.getKeyCode() == KeyEvent.KEYCODE_DPAD_RIGHT) {
}
}
这是部分代码,也就是需要判断当前按下的操作是那个,这里面都有具体的code,看名字就知道这是什么东西了。(前提是你熟悉过焦点的code相关的api,这个可以查)
我怎么知道我在最右边了?
查找下一个焦点的view是null的时候,就是卡主(最右边)
//固定api,第一个参数当前vg,第二个当前有焦点的view,第三个方向
View nextFocus = FocusFinder.getInstance().findNextFocus(this, findFocus(), View.FOCUS_RIGHT);
//如果结果是null,那就是到了右边的尽头
我在哪里?我是谁
这个可以通过rv的api反查,然后找到下一个就ok了。
View currentFocus = findFocus();
if (currentFocus != null) {
//得到当前聚焦的view,通过管理器,反查当前的位置
int position = getLayoutManager().getPosition(currentFocus);
//我们需要聚焦的view是当前position + 1 的位置.但是需要判断是否越界了
//得到总view数目,然后判断越界情况
int itemCount = getAdapter().getItemCount();
int nextPosition = ++position;
if (nextPosition < itemCount) {
//在范围之内,然后取出这view,主动聚焦
View nextView = getLayoutManager().findViewByPosition(nextPosition);
if (nextView != null) {
nextView.requestFocus();
//消费这个事件
return true;
}
}
}
完整代码: Act
package com.mirageengine.tv.textrv;
import android.os.Bundle;
import androidx.appcompat.app.AppCompatActivity;
import androidx.recyclerview.widget.GridLayoutManager;
import androidx.recyclerview.widget.RecyclerView;
import java.util.ArrayList;
import java.util.List;
public class GridActivity extends AppCompatActivity {
private RecyclerView rvGrid;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_grid);
initView();
}
private void initView() {
rvGrid = (RecyclerView) findViewById(R.id.rv_grid);
rvGrid.setLayoutManager(new GridLayoutManager(this, 4));
GridDemoAdapter demoAdapter = new GridDemoAdapter();
rvGrid.setAdapter(demoAdapter);
List<String> list = new ArrayList<>();
list.add("大名");
list.add("大2");
list.add("大3");
list.add("大4");
list.add("大啊");
list.add("小明");
list.add("理解");
list.add("我是");
list.add("你好");
list.add("测试");
list.add("喜欢");
list.add("超越");
list.add("老龚");
list.add("你是");
list.add("拉了");
list.add("end");
demoAdapter.setNewData(list);
}
}
适配器:
package com.mirageengine.tv.textrv;
import com.chad.library.adapter.base.BaseQuickAdapter;
import com.chad.library.adapter.base.BaseViewHolder;
/**
* Create by os on 2021/9/19
* Desc :
*/
public class GridDemoAdapter extends BaseQuickAdapter<String, BaseViewHolder> {
public GridDemoAdapter() {
super(R.layout.item_grid_demo);
}
@Override
protected void convert(BaseViewHolder helper, String item) {
helper.setText(R.id.tv_name, item);
}
}
重新换行的rv:
package com.mirageengine.tv.textrv.rv;
import android.content.Context;
import android.util.AttributeSet;
import android.view.FocusFinder;
import android.view.KeyEvent;
import android.view.View;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.core.graphics.BitmapCompat;
import androidx.recyclerview.widget.RecyclerView;
/**
* Create by os on 2021/9/19
* Desc : 支持焦点最右边按右换行的rv
*/
public class GridRv extends RecyclerView {
private static final String TAG = "GridRv";
public GridRv(@NonNull Context context) {
super(context);
}
public GridRv(@NonNull Context context, @Nullable AttributeSet attrs) {
super(context, attrs);
}
public GridRv(@NonNull Context context, @Nullable AttributeSet attrs,
int defStyleAttr) {
super(context, attrs, defStyleAttr);
}
@Override
public boolean dispatchKeyEvent(KeyEvent event) {
//拦截右边的按键操作,然后查找出下一个item的位置,主动聚焦到改item上面
if (event.getAction() == KeyEvent.ACTION_DOWN) {
//如果是遥控器按键按下的操作
if (event.getKeyCode() == KeyEvent.KEYCODE_DPAD_RIGHT) {
//向右查找,找不到下一个聚焦的view了,这个时候就是右边边沿了
//这里我们寻找下一个焦点位置(指定方向右边)
//findFocus()是获取当前有焦点的view
View nextFocus = FocusFinder.getInstance().findNextFocus(this, findFocus(), View.FOCUS_RIGHT);
//在按下的情况下,再过滤右??的方向
if (nextFocus == null) {
//如果向右找不到了,那就证明=>右边边沿了,我们进行换行
View currentFocus = findFocus();
if (currentFocus != null) {
//得到当前聚焦的view,通过管理器,反查当前的位置
int position = getLayoutManager().getPosition(currentFocus);
//我们需要聚焦的view是当前position + 1 的位置.但是需要判断是否越界了
//得到总view数目,然后判断越界情况
int itemCount = getAdapter().getItemCount();
int nextPosition = ++position;
if (nextPosition < itemCount) {
//在范围之内,然后取出这view,主动聚焦
View nextView = getLayoutManager().findViewByPosition(nextPosition);
if (nextView != null) {
nextView.requestFocus();
//消费这个事件
return true;
}
}
}
}
}
//其他方向的判断
}
return super.dispatchKeyEvent(event);
}
}
最后的效果图:
总结
我们需要连接vg的事件,适合的条件下我们加入自己的流程 如果查找下一个焦点位置 如果查找当前vg的有焦点的位置 rv如何通过view反查position和下一个view 如何主动聚焦
项目地址 https://gitee.com/dong_rong/android-tv-sample