一、ListView优化
列表的显示需要三个元素:
- ListVeiw: 用来展示列表的View。
- 适配器 : 用来把数据映射到ListView上
- 数据: 具体的将被映射的字符串,图片,或者基本组件。
适配器
- ArrayAdapter:只能展示一行字
- SimpleAdapter:扩充性最好
- BaseAdapter:自定义Adapter
ListView加载数据原理
系统要绘制ListView,首先调用getCount()函数得到ListView要显示的总条目数,再绘制Item条目,调用getView()函数。获得一个View(加载item要显示的布局),然后再实例化并设置各个组件及其数据内容并显示它。
ListView 针对每个item,要求 adapter “返回一个视图” (getView),也就是说ListView在开始绘制的时候,系统首先调用getCount()函数,根据他的返回值得到ListView的长度,然后根据这个长度,调用getView()一行一行的绘制ListView的每一项。
ListView优化原理
- 使用convertView可以避免重复地调用inflate
- 使用ViewHolder可以避免重复地调用findViewById
ListView中的每一个Item显示都需要Adapter调用一次getView的方法,这个方法会传入一个convertView的参数,返回的View就是这个Item显示的View。如果当Item的数量足够大,再为每一个Item都创建一个View对象,必将占用很多内存,创建View对象(mInflater.inflate(R.layout.lv_item, null);从xml中生成View,这是属于IO操作)也是耗时操作,所以必将影响性能。Android提供了一个叫做Recycler(反复循环器)的 构件,就是当ListView的Item从上方滚出屏幕视角之外,对应Item的View会被缓存到Recycler中,相应的会从下方生成一个 Item,而此时调用的getView中的convertView参数就是滚出屏幕的Item的View,所以说如果能重用这个convertView, 就会大大改善性能。
- convertView复用:判断convertView是否为空,当convertView为空时不重复使用,为空时初始化,尽可能少创建view(View.inflate(….)的方法,将xml文件解析,并显示到界面上,这是非常消耗资源的)
- 定义一个viewHolder,封装Item条目中所需要展示的所有组件,给convertView设置tag(setTag()),传入一个viewHolder对象,用于缓存要显示Item条目,可以达到图像数据异步加载的效果。
- 如果listview需要显示的item很多,就要考虑分页加载。
二、ListView分页加载的实现
方式一:当用户滑动到底部时自动加载实现思路
实现OnScrollListener 接口重写onScrollStateChanged 和onScroll方法,使用onscroll方法实现”滑动“后处理检查是否还有新的记录,如果有,添加记录到adapter, adapter调用 notifyDataSetChanged 更新数据;如果没有记录了,则不再加载数据。使用onScrollStateChanged可以检测是否滚到最后一行且停止滚动然后执行加载.
/**
* 滚动监听方法
*
*/
@Override
public void onScroll(AbsListView view, int firstVisibleItem,
int visibleItemCount, int totalItemCount) {
isLoad = ((firstVisibleItem + visibleItemCount) == totalItemCount);
}
/**
* 滑动改变的监听事件
*/
@Override
public void onScrollStateChanged(AbsListView view, int scrollState) {
if (isLoad
&& scrollState == android.widget.NumberPicker.OnScrollListener.SCROLL_STATE_IDLE) {
}
}
方式二:在ListView底部设置一个按钮,用户点击即加载实现思路
/**
* 1、Button布局不能和ListView不在在同一布局
* 2、通过inflate方法添加带有button的布局
* 3、通过ListView的addFooterView()方法将其添加到listView底部
* 4、设置button的点击事件,切不能再onScrollStateChanged()方法中
* 5、创建Hander来更新加载出来的数据
*/
View view = View.inflate(getApplicationContext(),
R.layout.footerlayout, null);
bt = (Button) view.findViewById(R.id.bt);
lv.addFooterView(view);
bt.setOnClickListener(new OnClickListener() {
@Override
public void onClick(View v) {
handler.postDelayed(new Runnable() {
@Override
public void run() {
// 添加更新的数据
// 其方法中的list集合必须创建成全局的
initData("string" + count);
count++;
// 加载更多数据
bt.setVisibility(View.VISIBLE);
// pg.setVisibility(View.GONE);
// 通知listView刷新数据
adapter.notifyDataSetChanged();
}
}, 2000);
}
});
三、ListView图片异步加载实现思路
- 先从内存缓存中获取图片显示(内存缓冲)
- 获取不到的话从SD卡里获取(SD卡缓冲,,从SD卡获取图片是放在子线程里执行的,否则快速滑屏的话会不够流畅)
- 都获取不到的话从网络下载图片并保存到SD卡同时加入内存并显示(视情况看是否要显示)
这里可能出现图片错位的现象
图片错位问题的本质源于我们的listview使用了缓存convertView,假设一种场景,一个listview一屏显示九个item,那么在拉出第十个item的时候,事实上该item是重复使用了第一个item,也就是说在第一个item从网络中下载图片并最终要显示的时候,其实该item已经不在当前显示区域内了,此时显示的后果将可能在第十个item上输出图像,这就导致了图片错位的问题。所以解决之道在于可见则显示,不可见则不显示。在ImageLoader里有个imageViews的map对象,就是用于保存当前显示区域图像对应的url集,在显示前判断处理一下即可。
四、ListView 加载不同条目布局
ListView 显示的每个条目都是通过 baseAdapter 的
getView(int position, View convertView,ViewGroup parent)
来展示的,除此之外 adapter 还提供了getViewTypeCount()和 getItemViewType(int position) 两个方法。在 getView 方法中我们可以根据不同 的viewtype 加载不同的布局文件。
五、ListView 数据集改变后,如何更新 ListView
使用该 ListView 的 adapter 的 notifyDataSetChanged()方法。该方法会使 ListView 重新绘制
六、ListView 如何定位到指定位置
可以通过 ListView 提供的 lv.setSelection(48);方法
七、ScrollView和ListView的冲突问题
http://bbs.anzhuo.cn/thread-982250-1-1.html
1. 不光是ListView,其他继承自AbsListView的类也会出现相同的问题,包括:ExpandableListView、GridView等,为了方便说明,以下均用ListView来代表
2. ScrollView中只能放一个控件,一般都放LinearLayout中,orientation属性值为vertical。
scrollview和listview的冲突 listview显示不全
方法一: 默认情况下Android是禁止在ScrollView中放入另外的ScrollView,因为它的高度无法计算
解决方法:ListView设置完Adapter后,根据ListView的子条目重新计算ListView的高度,然后把高度再作为LayoutParms设置给ListIView
public class Utility {
public static void setListViewHeightBasedOnChildren(ListView listView) {
// 获取ListView对应的Adapter
ListAdapter listAdapter = listView.getAdapter();
if(listAdapter == null) {
return;
}
int totalHeight = 0;
for(int i = 0, len = listAdapter.getCount(); i < len; i++) { // listAdapter.getCount()返回数据项的数目
View listItem = listAdapter.getView(i, null, listView);
listItem.measure(0, 0); // 计算子项View 的宽高
totalHeight += listItem.getMeasuredHeight(); // 统计所有子项的总高度
}
ViewGroup.LayoutParams params = listView.getLayoutParams();
params.height = totalHeight
+ (listView.getDividerHeight() * (listAdapter.getCount() - 1));
// listView.getDividerHeight()获取子项间分隔符占用的高度
// params.height最后得到整个ListView完整显示需要的高度
listView.setLayoutParams(params);
}
}
只要在设置ListView的Adapter后调用此静态方法即可让ListView正确的显示在其父ListView的ListItem中。但是要注意的是,子ListView的每个Item必须是LinearLayout,不能是其他的,因为其他的Layout(如RelativeLayout)没有重写onMeasure(), 所以会在onMeasure()时抛出异常。
方法二: 自定义ListView,重写onMeasure方法,引用该ListView即可。
public void onMeasure(int widthMeasureSpec, int heightMeasureSpec)
{
int mExpandSpec = MeasureSpec.makeMeasureSpec(Integer.MAX_VALUE >> 2,
MeasureSpec.AT_MOST);
super.onMeasure(widthMeasureSpec, mExpandSpec);
}
scrollview和listview的滑动冲突
给ScrollView 设置 android:fillViewport="true"
//当ScrollView里的元素想填满ScrollView时,使用"fill_parent"是不管用的,必需为ScrollView设置:android:fillViewport="true"
自定义ListView,在listView的onTouch里面拦截ScrollView的事件,使得scrollView不可以获得点击事件,这样只有listView获得点击事件!
listView.setOnTouchListener(new OnTouchListener() {
@Override
public boolean onTouch(View arg0, MotionEvent arg1) {
scrollView.requestDisallowInterceptTouchEvent(true);
return false;
}
});
方法二:也可以不自定义ListView,为ListView设置OnTouchListener()的OnTouch方法中调用sv.requestDisallowInterceptTouchEvent(true);