VerticalGridView是什么?
VerticalGridView是安卓leanback库的列表组件,用于支持使用遥控器(按键事件)浏览列表。
它与RecyclerView的继承关系是:VerticalGridView→BaseGridView→RecyclerView
首先我想吐槽一下leanback的BaseGridView相关组件,耦合度较高,并且不允许开发者替换其LayoutManager,且该GridLayoutManager是final修饰,不允许继承。
这样出了bug都没法给他改。
异常现象
如图所示的VerticalGridView中,使用手指触屏滑动时,会出现滑出一定距离就被重置回起点的问题,永远无法滑动到界面底部。
原因
起初是怀疑和布局刷新有关,因为在bindView中不给ImageView设置图片就没有该问题了。
后来看了下GridLayoutManager的代码,log日志打开后发现,每当触控下滑到一定距离,就会触发onLayoutChildren()方法,在这个方法中,会调用focusToViewInLayout()方法,该方法是用于,当用户使用遥控器上下左右移动焦点时,将VerticalGridView滚动至以焦点View为中心的位置。
这个逻辑在使用遥控器时是没有问题的,是需要的,但是在进行触摸事件时,却是会导致问题的。
因为触摸滑动过程中是没有产生焦点,没有移动焦点的,此时如果按照原逻辑,就会出现滑动时被无限重置回焦点位置的问题。
解决方案
问题原因是GridLayoutManager在onLayoutChildren时,会将视图定位到焦点所在的view,导致无法往下滑动。
解决方案就是区分出当前的onLayoutChildren方法是由于按键事件导致的,还是触摸事件导致的,
如果是触摸事件导致的,那么就不需要调用focusToViewInLayout()方法。
但是此处代码无法获取事件的来源信息,我们只能自行监听触摸事件,在用户触摸中不允许focusToViewInLayout()方法的调用即可。
代码修改
我们需要将该组件抽取到自己的项目中,才能修改里面代码。
implementation 'androidx.leanback:leanback-grid:1.0.0-alpha03'
先把上面库的文件copy到自己项目中。
然后在GridLayoutManager中声明变量
boolean isDragging = false;
Handler mDragHandler = new Handler();
初始化时监听触摸事件
@SuppressLint("WrongConstant")
GridLayoutManager(@Nullable BaseGridView baseGridView) {
mBaseGridView = baseGridView;
mChildVisibility = -1;
// disable prefetch by default, prefetch causes regression on low power chipset
setItemPrefetchEnabled(false);
setOnTouchInterceptListener();
}
void setGridView(BaseGridView baseGridView) {
mBaseGridView = baseGridView;
mGrid = null;
setOnTouchInterceptListener();
}
void setOnTouchInterceptListener(){
mBaseGridView.setOnTouchInterceptListener(event -> {
if (event.getAction() == MotionEvent.ACTION_DOWN) {
Log.i(TAG, "mBaseGridView OnTouchInterceptListener: isDragging = true");
mDragHandler.removeCallbacksAndMessages(null);
isDragging = true;
}
if (event.getAction() == MotionEvent.ACTION_UP) {
mDragHandler.postDelayed(() -> {
Log.i(TAG, "mBaseGridView OnTouchInterceptListener: isDragging = false");
isDragging = false;
}, 1000);
}
return false;
});
}
在焦点定位的方法中,判断该变量进行拦截
private void focusToViewInLayout(boolean hadFocus, boolean alignToView, int extraDelta,
int extraDeltaSecondary) {
if (isDragging) {
// 说明是触摸事件导致的重新布局,此时并不需要将视图定位到焦点View
return;
}
...
}
除此之外,GridLayoutManager在触摸屏环境下还存在其他问题,比如你设置了固定的方向,但VerticalGridView仍然可以水平方向拖动。
该问题的解决方法:修改canScrollVertically和canScrollHorizontally方法,去掉后面的条件,只判断方向即可。
可以看出leanback库中的这些组件是完全没有考虑触控环境下的使用的,如果想要在触屏下使用,必须自行修改。