在android开发中Listview是一个很重要的组件,它以列表的形式根据数据的长自适应展示具体内容,用户可以自由的定义listview每一列的布局,但当listview有大量的数据需要加载的时候,会占据大量内存,影响性能。
本文的重点即是从如下几个方面介绍如何对ListView进行优化。
1、convertView重用
the old view to reuse, if possible. Note: You should check that this view is non-null and of an appropriate type before using. If it is not possible to convert this view to display the correct data, this method can create a new view
利用好 convertView 来重用 View,切忌每次 getView() 都新建。ListView 的核心原理就是重用 View,如果重用 view 不改变宽高,重用View可以减少重新分配缓存造成的内存频繁分配/回收;
<?xml version="1.0" encoding="utf-8"?><LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="fill_parent" android:layout_height="fill_parent" android:orientation="vertical" > <ListView android:id="@+id/listview" android:layout_width="fill_parent" android:layout_height="fill_parent" android:cacheColorHint="#00000000" ></ListView></LinearLayout>ListView的android:layout_height属性值设置为"fill_parent"或者''wrap_content"情况不一样,但是convertView的机制一样
如果设置为fill_parent:屏幕上显示出的Item的convertview都为空,向下滑动新产生的Item的convetview都不为空
如果设置为wrap_content:只有第一个Item的convertview为null,其他的不为空
总结:
在初始显示的时候,每次显示一个item都调用一次getview方法但是每次调用的时候covertview为空(因为还没有旧的view),当显示完了之后。如果屏幕移动了之后,并且导致有些Item(也可以说是view)跑到屏幕外面,此时如果还有新的item需要产生,则这些item显示时调用的getview方法中的convertview参数就不是null,而是那些移出屏幕的view(旧view),我们所要做的就是将需要显示的item填充到这些回收的view(旧view)中去,最后注意convertview为null的不仅仅是初始显示的那些item,还有一些是已经开始移入屏幕但是还没有view被回收的那些item。
2、ViewHolder优化
view的setTag和getTag方法其实很简单,在实际编写代码的时候一个view不仅仅是为了显示一些字符串、图片,有时我们还需要他们携带一些其他的数据以便我们对该view的识别或者其他操作。于是android 的设计者们就创造了setTag(Object)方法来存放一些数据和view绑定,我们可以理解为这个是view 的标签也可以理解为view 作为一个容器存放了一些数据。而这些数据我们也可以通过getTag() 方法来取出来。
到这里setTag和getTag大家应该已经明白了。再回到上面的话题,我们通过convertview的setTag方法和getTag方法来将我们要显示的数据来绑定在convertview上。如果convertview 是第一次展示我们就创建新的Holder对象与之绑定,并在最后通过return convertview 返回,去显示;如果convertview 是回收来的那么我们就不必创建新的holder对象,只需要把原来的绑定的holder取出加上新的数据就行了
class ViewHolder{ ImageView img; TextView name;}public View getView(int position, View convertView, ViewGroup parent) { ViewHolder holder = null; if(convertView==null){ convertView = inflater.inflate(R.layout.list_item, parent, false); holder.img = (ImageView) convertView.findViewById(R.id.img); holder.name = (TextView) convertView.findViewById(R.id.name); holder = new ViewHolder(); convertView.setTag(holder); }else{ holder = (ViewHolder) convertView.getTag(); } //设置holder holder.img.setImageResource(R.drawable.ic_launcher); holder.name.setText(list.get(position).partname); return convertView;}
3、图片加载优化
/** * list滚动监听 */ listView.setOnScrollListener(new OnScrollListener() { @Override public void onScrollStateChanged(AbsListView view, int scrollState) { if (scrollState == OnScrollListener.SCROLL_STATE_IDLE) {//list停止滚动时加载图片 loadImage(startPos, endPos);// 异步加载图片 ,只加载可以看到的图片 } } @Override public void onScroll(AbsListView view, int firstVisibleItem, int visibleItemCount, int totalItemCount) { //设置当前屏幕显示的起始pos和结束pos startPos = firstVisibleItem; endPos = firstVisibleItem + visibleItemCount; if (endPos >= totalItemCount) { endPos = totalItemCount - 1; } } });
listView = (ListView) rootView.findViewById(R.id.fragment_user_info_lisiview); listView.setOnScrollListener(DisplayImageOptionsUtil.getPauseOnScrollListener(this)); listView.setOnItemClickListener(this);
public static PauseOnScrollListener getPauseOnScrollListener(OnScrollListener scrollListener) { PauseOnScrollListener listener = new PauseOnScrollListener(ImageLoader.getInstance(), false, true, scrollListener); return listener; }PauseOnScrollListener的第一个参数指的是图片加载对象ImageLoader,第二个参数为pauseOnScroll来控制是否在滑动的过程中暂停加载图片,如果需要暂停则传true,第三个参数控制猛的滑动界面的时候图片是否加载。
/** * Constructor * * @param imageLoader [email protected] ImageLoader} instance for controlling * @param pauseOnScroll Whether [email protected] ImageLoader#pause() pause ImageLoader} during touch scrolling * @param pauseOnFling Whether [email protected] ImageLoader#pause() pause ImageLoader} during fling * @param customListener Your custom [email protected] OnScrollListener} for [email protected] AbsListView list view} which also * will be get scroll events */ public PauseOnScrollListener(ImageLoader imageLoader, boolean pauseOnScroll, boolean pauseOnFling, OnScrollListener customListener) { this.imageLoader = imageLoader; this.pauseOnScroll = pauseOnScroll; this.pauseOnFling = pauseOnFling; externalListener = customListener; } @Override public void onScrollStateChanged(AbsListView view, int scrollState) { switch (scrollState) { case OnScrollListener.SCROLL_STATE_IDLE: imageLoader.resume(); break; case OnScrollListener.SCROLL_STATE_TOUCH_SCROLL: if (pauseOnScroll) { imageLoader.pause(); } break; case OnScrollListener.SCROLL_STATE_FLING: if (pauseOnFling) { imageLoader.pause(); } break; } if (externalListener != null) { externalListener.onScrollStateChanged(view, scrollState); } }
4、onClickListener处理
holder.img.setonClickListener(new onClickListenr)...但是这种写法每次调用getView时都设置了一个新的onClick事件,效率很低。高效的写法可以直接在ViewHolder中设置一个position,然后viewHolder implements OnClickListenr:
class ViewHolder implements OnClickListener{ int position; TextView name; public void setPosition(int position){ this.position = position; } @Override public void onClick(View v) { switch (v.getId()){ //XXXX } }}public View getView(int position, View convertView, ViewGroup parent) { ViewHolder holder = null; if(convertView==null){ convertView = inflater.inflate(R.layout.list_item, parent, false); holder = new ViewHolder(); holder.name = (TextView) convertView.findViewById(R.id.name); holder.name.setOnClickListener(this); convertView.setTag(holder); }else{ holder = (ViewHolder) convertView.getTag(); } //设置holder holder.name.setText(list.get(position).partname); //设置position holder.setPosition(position); return convertView;}
补充:ListView的listitem里面含有Button CheckBox之类的子控件的时候,子控件会把Focus抢去,最简单有效的解决方法是在ListView的item布局文件根元素中设置属性 android:descendantFocusability="blocksDescendants"
5、减少Item View的布局层级
6、adapter中的getView方法尽量少使用逻辑
@Overridepublic View getView(int position, View convertView, ViewGroup paramViewGroup) { Object current_event = mObjects.get(position); ViewHolder holder = null; if (convertView == null) { holder = new ViewHolder(); convertView = inflater.inflate(R.layout.row_event, null); holder.ThreeDimension = (ImageView) convertView.findViewById(R.id.ThreeDim); holder.EventPoster = (ImageView) convertView.findViewById(R.id.EventPoster); convertView.setTag(holder); } else { holder = (ViewHolder) convertView.getTag(); } //在这里进行逻辑判断,这是有问题的 if (doesSomeComplexChecking()) { holder.ThreeDimention.setVisibility(View.VISIBLE); } else { holder.ThreeDimention.setVisibility(View.GONE); } // 这是设置image的参数,每次getView方法执行时都会执行这段代码,这显然是有问题的 RelativeLayout.LayoutParams imageParams = new RelativeLayout.LayoutParams(measuredwidth, rowHeight); holder.EventPoster.setLayoutParams(imageParams); return convertView;}优化后的getView():
@Overridepublic View getView(int position, View convertView, ViewGroup paramViewGroup) { Object object = mObjects.get(position); ViewHolder holder = null; if (convertView == null) { holder = new ViewHolder(); convertView = inflater.inflate(R.layout.row_event, null); holder.ThreeDimension = (ImageView) convertView.findViewById(R.id.ThreeDim); holder.EventPoster = (ImageView) convertView.findViewById(R.id.EventPoster); //设置参数提到这里,只有第一次的时候会执行,之后会复用 RelativeLayout.LayoutParams imageParams = new RelativeLayout.LayoutParams(measuredwidth, rowHeight); holder.EventPoster.setLayoutParams(imageParams); convertView.setTag(holder); } else { holder = (ViewHolder) convertView.getTag(); } // 我们直接通过对象的getter方法代替刚才那些逻辑判断,那些逻辑判断放到别的地方去执行了 holder.ThreeDimension.setVisibility(object.getVisibility()); return convertView;}
7、adapter中的getView方法尽量少做耗时操作
8、adapter中的getView方法避免创建大量对象
9、将ListView的scrollingCache和animateCache设置为false
其它
版权声明:本文为博主原创文章,未经博主允许不得转载。