存档

文章标签 ‘listview’

Android中焦点移到ListView的问题

2010年2月25日 2 条评论

发现Android编程中的一个问题:如果在一个ListView上面放置一个可以接收焦点的东西,比如Button,当使用向上方向键滚动ListView到第一条后,焦点会移到上面的Button上,这个没问题。但然后使用向下的方向键时,焦点会跳到ListView中当前窗口的最下面一条,而不是焦点离开时的第一条。在ListView下方有Button的时候,向上移动焦点,也会出现类似的情况。

这个问题在Android的示例里面也有,ApiDemos->Views->Tabs->Content By Intent。这个示例里当使用方向键从list这个Tab向下移动焦点的时候,会跳过一屏的条目。

在网上搜了一下,仅仅有一个人提到了这个问题,但没有看到解答。

我查了一下源代码,实现设置焦点的代码是:

git://android.git.kernel.org/platform/frameworks/base.git›core›java›android›widget›ListView.java

@Override
protected void onFocusChanged(boolean gainFocus, int direction, Rect previouslyFocusedRect) {
    super.onFocusChanged(gainFocus, direction, previouslyFocusedRect);

    int closetChildIndex = -1;
    if (gainFocus && previouslyFocusedRect != null) {
        previouslyFocusedRect.offset(mScrollX, mScrollY);

        // figure out which item should be selected based on previously
        // focused rect
        Rect otherRect = mTempRect;
        int minDistance = Integer.MAX_VALUE;
        final int childCount = getChildCount();
        final int firstPosition = mFirstPosition;
        final ListAdapter adapter = mAdapter;

        for (int i = 0; i < childCount; i++) {
            // only consider selectable views
            if (!adapter.isEnabled(firstPosition + i)) {
                continue;
            }

            View other = getChildAt(i);
            other.getDrawingRect(otherRect);
            offsetDescendantRectToMyCoords(other, otherRect);
            int distance = getDistance(previouslyFocusedRect, otherRect, direction);

            if (distance < minDistance) {
                minDistance = distance;
                closetChildIndex = i;
            }
        }
    }

    if (closetChildIndex >= 0) {
        setSelection(closetChildIndex + mFirstPosition);
    } else {
        requestLayout();
    }
}

通过debug发现,previouslyFocusedRect在这里是ListView的,而不是之前焦点View的。在按向下键时,getDistance比较ListView的bottom和各个child的top,当然会选中离ListView下沿最近的。具体为什么会previouslyFocusedRect是ListView,我还没有深入分析。

一个解决办法

这不是一个根本解决的方法:写一个新的class,继承ListView,覆盖onFocusChanged。

@Override
protected void onFocusChanged(boolean gainFocus, int direction, Rect previouslyFocusedRect) {
	super.onFocusChanged(gainFocus, direction, previouslyFocusedRect);
	if (gainFocus && previouslyFocusedRect != null) {
		final ListAdapter adapter = getAdapter();
		final int count = adapter.getCount();
		switch (direction) {
			case FOCUS_DOWN:
				for (int i = 0; i < count; i++) {
					if (!adapter.isEnabled(i)) {
						continue;
					}
					setSelection(i);
					break;
				}
				break;
			case FOCUS_UP:
				for (int i = count-1; i>=0; i--) {
					if (!adapter.isEnabled(i)) {
						continue;
					}
					setSelection(i);
					break;
				}
				break;
			default:
				break;
		}
	}
}

在这里,我只处理了FOCUS_DOWN和FOCUS_UP。由于不能访问mFirstPosition,处理也做了简化:焦点从上方移下来时选择第一个能选择的,从下方移上来时选择最后一个能选择的。

分类: Android 标签: