getChildAt
Listview可以有header有footer,在普通listview的上下方,随着滚动会出现或者消失
Listview的mChildren是可见部分的item(可以是header,footer)的集合,所以ListView.getChildAt(int position),
这个position指的是在可视的item中的索引,跟cursor里的位置是大不一样的。可以看看ListView.getChildCount()函数得到个数是小于或等于Cursor里的个数的(不考虑header的话)。虽然一共可能有20条数据,但是界面只能看到8条,那么这个ChildCount大约就是8了。
getTop
View firstChild =view.getChildAt(0);
int top = firstChild.getTop();
这段代码是获取第一个可见项与listview本身的高度距离,如图,测试数据1有部分在屏幕外,有部分在屏幕内,假设在屏幕外部分为20px,屏幕内部分为30px,那他的top值就是-20px
listview滚动距离
我们怎么知道一个listview滚动了多少呢,可能会先想到视图的基类方法getScrollY(),但此方法给ScrollView使用没有问题,因为ScrollView没有复用机制。ListView的父容器里永远只有可见的item,整个容器其实并没有相对屏幕移动,因此getScrollY()总是为0。
有方法如下:
public int getScrollY() {
View c = mListView.getChildAt(0);
if (c == null) {
return 0;
}
int firstVisiblePosition = mListView.getFirstVisiblePosition();
int top = c.getTop();
return -top + firstVisiblePosition * c.getHeight() ;
}
这个方法只适用于所有的item一样高的
分割线问题
改变数据size的操作必须在主线程
listview对应的数据arraylist,会由一些操作,比如add,remove,这些操作会改变数组的size,尽量把这些操作和notifyDataSetChanged一起放到ui线程中去,如果数据操作在非ui线程内,可能会导致崩溃,日志如下
E/AndroidRuntime(16779):java.lang.IllegalStateException: The content of the adapter has changed butListView did not receive a notification. Make sure the content of your adapteris not modified from a background thread, but only from the UI thread. Makesure your adapter calls notifyDataSetChanged() when its content changes.
这个崩溃是listview的layoutChildren触发的,代码如下
- if (mItemCount == 0) {
- resetList();
- invokeOnItemScrollListener();
- return;
- } else if (mItemCount != mAdapter.getCount()) {
- throw newIllegalStateException("The content of the adapter has changed but "
- + "ListView didnot receive a notification. Make sure the content of "
- + "your adapter isnot modified from a background thread, but only from "
- + "the UI thread.Make sure your adapter calls notifyDataSetChanged() "
- + "when itscontent changes. [in ListView(" + getId() + ", " + getClass()
- + ") withAdapter(" + mAdapter.getClass() + ")]");
- }
listview会在layoutChildren内部去检查mItemCount !=mAdapter.getCount()。
如果我们在子线程改变数据,在主线程内更新notifyDataSetChanged,这2个之间有时间差,如果在这2个操作之间 listview执行onLayout,就会调用layoutChildren,此时mAdapter.getCount()已经改变,但是还没有notifyDataSetChanged,所以mItemCount没有变化,导致崩溃
可参考http://www.cnblogs.com/monodin/p/3874147.html
ListView.setOnItemClickListener无效
如果你的自定义ListViewItem中有Button或者Checkable的子类控件的话,那么默认focus是交给了子控件,而ListView的Item能被选中的基础是它能获取Focus,也就是说我们可以通过将ListView中Item中包含的所有控件的focusable属性设置为false,这样的话ListView的Item自动获得了Focus的权限,也就可以被选中了,也就会响应onItemClickListener中的onItemClick()方法,然而将ListView的ItemLayout的子控件focusable属性设置为false有点繁琐,我们可以通过对ItemLayout的根控件设置其android:descendantFocusability=”blocksDescendants”即可,这样ItemLayout就屏蔽了所有子控件获取Focus的权限,不需要针对ItemLayout中的每一个控件重新设置focusable属性了,如此就可以顺利的响应onItemClickListener中的onItenClick()方法了。
ListView.setOnItemLongClickListener无效
setMovementMethod引起的ListView.setOnItemClickListener无效
导致
setFocusable(true);
setClickable(true);
setLongClickable(true);
滚动listview
public void smoothScrollToPositionFromTop(int position, int offset)
public void smoothScrollToPosition(int position)
注意这里有个参数是position,他是会把header算进来的,比如header有2个,那第0个item的position就是2+0
所以调用的时候应该这样smoothScrollToPosition(index+listview.getHeaderViewsCount())
然后smoothScrollToPosition这个方法是干嘛的?他是让这个item能够显示出来,也就是说如果此item不可见,listview会滚动直到他可见;如果item已经在当前视图里了,那这个方法不会做任何事情。
smoothScrollToPositionFromTop这个函数的作用就是不仅让item可见,并且让这个item距离listview的顶端一定距离,这个距离是offset,有的时候我们需要某item跑到listview的顶端,就可以用此函数
smoothScrollToPositionFromTop有个2参的有个3参的,不要用2参的,在api21上有bug 可见https://code.google.com/p/android/issues/detail?id=78030
所以我们只用三参的,duration一般写200就行
直接用smoothScrollToPosition的三参函数也还有问题,会引发跳转不准的问题,可以参考https://code.google.com/p/android/issues/detail?id=36062
最后我的解决方案是提供一个工具方法,里面写2个函数,需要用到smoothScrollToPositionFromTop的时候用这个工具方法
//针对smoothScrollToPositionFromTop的某些bug而写的smoothScrollToPositionFromTop,主要解决滚动不准bug,滚动太快或太慢的bug
public static void smoothScrollToPositionFromTop(final AbsListView view, final int position) {
View child = getChildAtPosition(view, position);
// There's no need to scroll if child is already at top or view is already scrolled to its end
if ((child != null) && ((child.getTop() == 0) || ((child.getTop() > 0) && !ViewCompat.canScrollVertically(view,1)))) {
return;
}
view.setOnScrollListener(new AbsListView.OnScrollListener() {
@Override
public void onScrollStateChanged(final AbsListView view, final int scrollState) {
if (scrollState == SCROLL_STATE_IDLE) {
view.setOnScrollListener(null);
// Fix for scrolling bug
new Handler().post(new Runnable() {
@Override
public void run() {
view.setSelection(position);
}
});
}
}
@Override
public void onScroll(final AbsListView view, final int firstVisibleItem, final int visibleItemCount,
final int totalItemCount) {
}
});
// Perform scrolling to position
new Handler().post(new Runnable() {
@Override
public void run() {
//add 200 parameter to fix scroll too fast bug
// 参考 https://code.google.com/p/android/issues/detail?id=78030
view.smoothScrollToPositionFromTop(position, 0, 200);
}
});
}
public static View getChildAtPosition(final AdapterView view, final int position) {
final int index = position - view.getFirstVisiblePosition();
if ((index >= 0) && (index < view.getChildCount())) {
return view.getChildAt(index);
} else {
return null;
}
}
这个解决方案主要参考
参考资料
http://blog.youkuaiyun.com/lilybaobei/article/details/8142987
http://blog.youkuaiyun.com/by317966834/article/details/8731579