文章目录

在项目里面,有一个页面,整体是一个ScrollView,展示的是一个配置页面,其中一个子控件是一张包含多个缩略图的大图。 近期,策划提出一个新需求,要把原本这个整张的缩略图拆开为一张张的小图,以便单独响应点击事件。初一看,一个GridView替上去不就完事了嘛,再改改GridView的OnMeasure,让它的高度自适应放到ScrollView里面,这活似乎就完成的妥妥的了。

但紧接着,奇怪的事情发生了,如果gridview里元素比较少时,表现正常,但如果元素较多,ScrollView可以滚动的时候,就发现每次进入这个Activity,都是定位到最底部,如果元素更多一点,位置则固定在了GridView的顶部。看了半天代码,我没有做scrollTo的操作啊,再把其他不相干的代码回滚,还是无效。在我的项目里面,ScrollView套GridView还有另外一个地方,但那里表现的好好的,开始那里GridView是在顶上,正常,我想,那把GridView放到最底下再试试,x,还是正常。

郁闷了,没辙,只好google一下。很快,找到解决办法了,GridView#setFcous(false)。一试,还真可以。这我就奇怪了,跟这有毛线关系?跟踪代码看看吧。

要滚动到GridView位置,肯定会调用到scrollTo吧,和setFocus有关系,肯定会有requestFocus的地方吧,那就在这两个地方下断点,看看调用堆栈。果然,原因立马就找到了。首先,ScrollView布局完成后,会调用requestFocus,跟踪View#requestFocu的代码,一路往下调用了:

private boolean requestFocusNoSearch(int direction, Rect previouslyFocusedRect);

void handleFocusGainInternal(int direction, Rect previouslyFocusedRect);

ScrollView#requestChildFocus(View child, View focused);

答案就在requestChildFocus这个函数里面:

1
2
3
4
5
6
7
// 滚动到请求focus的子控件处。
if (!mIsLayoutDirty) {
scrollToChild(focused);
} else {
// The child may not be laid out yet, we can't compute the scroll yet
mChildToScrollTo = focused;
}

其实也很好理解,就像你一个页面上放了一个EditText,当进入这个页面时,肯定会希望系统滚动到你的输入框这里。

剩下另外一个问题了:为什么另外一个页面把GridView放到最底下了,它却没有跟着滑下去呢?答案也很好找,因为处理就在挨着requestChildFocus这个函数下面的这个函数里。先看说明:

When looking for focus in children of a scroll view, need to be a little more careful not to give focus to something that is scrolled off screen. This is more expensive than the default android.view.ViewGroupimplementation, otherwise this behavior might have been made the default.

函数体:

1
2
3
4
5
6
7
8
protected boolean onRequestFocusInDescendants(int direction, Rect previouslyFocusedRect) {
...

// 如果不在显示范围内,跳走
if (isOffScreen(nextFocus)) return false;

...
}

验证一下,把在最底下的GridView放到中间来,再进去,果然就滚到GridView的位置了。

再想一想,既然是这样的原因,那其实只要在ScrollView里面放能获取Focus的控件,都会有这个问题,比如ListView,EditText等等,ScrollView会定位到它找到的第一个Focusable Child。
衍生一下,在解决问题的过程中,我还发现如果在初始化的时候如果先初始化GridView,设好adapter,然后在初始化adapter的数据源,且不调用BaseAdapter#notifyDataSetChanged,也不会有问题,为啥呢?答案在BaseAdapter的setAdapter函数里面,其中会有checkFocus,如果adapter getCount返回为0,GridView不能获得焦点。而GridView在layout时,它的数据源又已经有了,所以也能够正常的把所有元素布局和绘制出来。当然了,如果后面有数据改动,那么立马就会被打回原形。

文章目录