实际开发中,有需要用到ScrollView嵌套ListView的情况,这里就不解释为什么会用到这两种控件嵌套使用,总之这两种控件直接嵌套使用会出现问题,一般情况下出现的问题是listview的内容显示不全,它的滚动与ScrollView的滚动冲突
解决方案如下:
(1)自定义ListView,重写onMeasure()方法:
public class MyListViews extends ListView { public MyListViews(Context context) { super(context); } public MyListViews(Context context, AttributeSet attrs) { super(context, attrs); } public MyListViews(Context context, AttributeSet attrs, int defStyle) { super(context, attrs, defStyle); } @Override protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { int expandSpec = MeasureSpec.makeMeasureSpec( Integer.MAX_VALUE >> 2, MeasureSpec.AT_MOST); super.onMeasure(widthMeasureSpec, expandSpec); } }
上面代码的详解:http://blog.csdn.net/hanhailong726188/article/details/46136569,注意链接的文章最后一句:实际开发当中不管listview有多少条数据,都能一次性展现出来。
(2)手动设置ListView的高度
经过测量发现,如果xml中指定ListView的高度是可以解决这个问题的,但是ListView的高度是可变的,所以实际使用中还需要在代码中测量后手动的设置ListView的高度,在setAdapter之后调用下面的方法就可以了。
/** * 动态设置ListView组建的高度 */ public void setListViewHeightBasedOnChildren(ListView listView) { if (listView == null) { return; } ListAdapter listAdapter = listView.getAdapter(); if (listAdapter == null) { return; } int totalHeight = 0; for (int i = 0; i < listAdapter.getCount(); i++) { View listItem = listAdapter.getView(i, null, listView); listItem.measure(0, 0); totalHeight += listItem.getMeasuredHeight(); } ViewGroup.LayoutParams params = listView.getLayoutParams(); params.height = totalHeight + (listView.getDividerHeight() * (listAdapter.getCount() - 1)); // listView.getDividerHeight()获取子项间分隔符占用的高度 // params.height最后得到整个ListView完整显示需要的高度 listView.setLayoutParams(params); }但是这个方法有个两个细节需要注意:
一是Adapter中getView方法返回的View的必须由LinearLayout组成,因为只有LinearLayout才有measure()方法,如果使用其他的布局如RelativeLayout,在调用listItem.measure(0,0);时就会抛异常,因为除LinearLayout外的其他布局的这个方法就是直接抛异常的。
二是该方法使用于每一个item的高度是固定的,或者说内容能够很快就加载完成的情况。
比如我item就只是显示一张图片,item控件的高度使用自适应(wrap_content),在adapter里面加载图片的时候,使用的是ImageLoad去加载网络图片,由于是异步加载图片,所以在setAdapter之后就开始测量高度,测出来的高度不是实际的高度,而是item的图片的高度,比如我item的图片设置了默认值,那么测出来的高度就是默认图片的高度,入股没有设置默认值,那么测出来的高度就是0,所以手动设置高度在这种情况下,会出现显示不全或者多出一些空白来。
这种方法适用于item中的图片等控件的高度是确定值(比如80dp);也适用于控件高度不确定,但是内容是很快就加载出来的那种,比如用的图片是drawable里面的图片,这种情况能够测量出准确的高度。
以上两种方法是大多人都采用的,打开一个页面,会直接显示listview的内容,所以需要手动的把Scrollview滚动到最顶端mScrollView.smoothScrollTo(0,0);也可以直接让ListView失去焦点
mListView.setFocusable(false);我是用了很久,才发现当加载很多网络图片的时候,会出现listview的尾部多一下空白,或者加载不全。如果出现这种情况,建议使用下面一种。
(3)放弃,选择ListView加载header,footer来实现同样的效果
headLayout=getLayoutInflater().inflate(R.layout.detail_listview_head,null); coverListView= (ListView) findViewById(R.id.listview_toshow_cover); coverListView.addHeaderView(headLayout);这种对于加载网络图片等不确定高度的情况也适用,不会出现listview的尾部空白也不会出现加载不全
从内存使用,来谈谈上面的方法:
方法1和2会马上就加载完给adapter的所有内容,比如我传给adapter的数据有15条,那么他们会立即加载完这15条,之后的滚动,内存会持平,不会有所增减。
方法3就是原始态的ListView了,所以它加载数据的时候不会一次性加载完,而是加载用户看到的和即将要看到,这样对于内存更好一点,用户不看到的就不占用内存。
不过方法3在优化了ViewHolder之后,可能会出现内容错位的情况。
网上找了很多方法,并不适合我的程序,所以,考虑到我的item布局就一张图片,所以省去了item中ImageView的优化,每次都findviewById:
@Override public View getView(int position, View view, ViewGroup parent) { ViewHoder viewHoder; if (view == null) { viewHoder = new ViewHoder(); view = inflater.inflate(R.layout.item_detail_image_listview, null); // viewHoder.image = (ImageView) view.findViewById(R.id.image_item); view.setTag(viewHoder); } else { viewHoder = (ViewHoder) view.getTag(); } viewHoder.image = (ImageView) view.findViewById(R.id.image_item); String str = list.get(position); if (str != null && !str.equals("")) { ImageLoader.getInstance().displayImage(DBManager.IMAGE_PATH_SYSTEM + str, viewHoder.image, DB_OPTIONS); } return view; }
这个解决了错位的问题,不过不是最优的,以后找到更好的再来更新。
有人说ImageLoad不会出现错位,这个我也不清楚,当时测试的时候会出现,过了一段时间,发现不出现错位,暂时不清楚原因。
如果不是有ImageLoad加载图片,解决重复使用是这样解决的:
@Override public View getView(final int position, View convertView, ViewGroup parent) { ViewHolder holder = null; if(convertView == null){ convertView = mInflater.inflate(R.layout.item_listview,null); holder = new ViewHolder(); holder.imageView = (ImageView) convertView.findViewById(R.id.item_img); holder.textView = (TextView) convertView.findViewById(R.id.item_tv); convertView.setTag(holder); }else { holder = (ViewHolder) convertView.getTag(); } holder.textView.setText(position+""); final ImageView imageView = holder.imageView; imageView.setTag(position+""); imageView.setImageDrawable(null); //前两个显示图片 if(position < 2) { //模仿耗时操作,延迟1秒 mHandler.postDelayed(new Runnable() { @Override public void run() { //获得组件当前的tag,与传入的内容比较,一直的才能显示 if(imageView.getTag().equals(position+"")) { imageView.setImageBitmap(mBitmap); } } }, 1000); } return convertView; }