listview常见问题

 

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滚动距离

参考例子 ListViewScrollToHead

我们怎么知道一个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一样高的

 

分割线问题

使用listview自带的分割线经常出现奇怪的问题,比如顶部多了跟线(比如item数量为0,只有head),比如底部少了跟线,可以看下面的文章

http://blog.csdn.net/xiaoxiaobian3310903/article/details/7182231
http://www.cnblogs.com/mengshu-lbq/archive/2012/04/07/2435883.html
http://gundumw100.iteye.com/blog/1169065

我觉得还是不要用他的线好了,自己在viewholder里面画跟线得了

改变数据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触发的,代码如下

[java]  view plain copy
  1. if (mItemCount == 0) {  
  2.         resetList();  
  3.         invokeOnItemScrollListener();  
  4.         return;  
  5.    } else if (mItemCount != mAdapter.getCount()) {  
  6.         throw newIllegalStateException("The content of the adapter has changed but "  
  7.                 + "ListView didnot receive a notification. Make sure the content of "  
  8.                 + "your adapter isnot modified from a background thread, but only from "  
  9.                 + "the UI thread.Make sure your adapter calls notifyDataSetChanged() "  
  10.                 + "when itscontent changes. [in ListView(" + getId() + ", " + getClass()  
  11.                 + ") withAdapter(" + mAdapter.getClass() + ")]");  
  12.    }  

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无效

有个问题,我设置了setOnItemLongClickListener,同时我也在adapter里面设置了item的onClick函数,注意设置一个item的点击效果可以使用listView.setOnItemClickListener,也可以在adapter里使用setOnClickListener,我这里使用后者。
按我的想法此时点击和长按都应该有效果,但是实际上点击有效果,而长按无效。

原因:
listView.setOnItemClickListener和listView.setOnItemLongClickListener这2个回调什么时候回执行,是在ListView的onTouchEvent里面执行的,也就是说只有item 的onTouchEvent返回false,才能轮到listview的onTouchEvent。而只要item设置了onClick,那么item的onTouchEvent一定返回true(可以参考文章android事件分发)。此处我们就设置了item的onClick,那么永远不会触发listview的onTouchEvent。所以setOnItemLongClickListener不会调用

setMovementMethod引起的ListView.setOnItemClickListener无效

如果listview的item内有TextView,用了这句textview.setMovementMethod(LinkMovementMethod.getInstance());
导致fixFocusableAndClickableSettings
导致         
setFocusable(true);
setClickable(true);
setLongClickable(true);
所以这个textview就变成抢焦点的了,当然setOnItemClickListener会无效
解决方案,不要用setOnItemClickListener和setOnItemLongClickListener了,直接在adapter内写textview的点击和长按事件,会少很多问题\

滚动listview

滚动listview有好几种方法,这里主要讲2个方法smoothScrollToPosition,smoothScrollToPositionFromTop
    public void smoothScrollToPositionFromTop(int position, int offset, int duration)
      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.csdn.net/lilybaobei/article/details/8142987

http://blog.csdn.net/by317966834/article/details/8731579


  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值