listview可以说是Android开发中最常见的UI控件了,listview能够以列表的方式显示大量同类的数据,这样问题就产生了,既然是大量数据,就会使用到很多布局,给布局绑定数据,listview将占用大量资源还可能会产生卡顿现象。
当系统开始绘制ListView的时候,首先调用getCount()方法。得到它的返回值,即ListView的长度。然后系统调用getView()方法,根据这个长度逐一绘制ListView的每一行。也就是说,如果让getCount()返回1,那么只显示一行。而getItem()和getItemId()则在需要处理和取得Adapter中的数据时调用。那么getView如何使用呢?如果有10000行数据,就绘制10000次?这肯定会极大的消耗资源,导致ListView滑动非常的慢,那应该怎么做呢?通过一个例子来讲解如何在使用BaseAdapter的时候优化ListView的显示。例子中将上一节中的ImageView换成Button,并且处理Button的点击事件,其中对ListView的显示做了优化。
(1)减少Item的view的次数,即:减少inflater.inflate(R.layout.list_item_icon_text, null);
<pre name="code" class="html"> public View getView(int position, View convertView, ViewGroup parent) {
if (convertView == null) {
convertView = mInflater.inflate(R.layout.list_item_icon_text,null);
}
else{
//具体操作
<span style="white-space:pre"> </span>}
<span style="white-space:pre"> </span> return convertView;
}
(2)减少item里面的每个空间的findViewById的次数。
在Adapter中使用静态的ViewHolder,具体代码如下:
static class ViewHolder {
TextView text;
ImageView icon;
}
@Override
public View getView(int position, View convertView, ViewGroup parent) {
ViewHolder holder;
if (convertView == null) {
holder = new ViewHolder();
convertView = mInflater.inflate(R.layout.list_item_icon_text,null);
holder.icon = (ImageView) convertView.findViewById(R.id.icon);
holder.text = (TextView) convertView.findViewById(R.id.text);
convertView.setTag(holder);
}
holder = (ViewHolder)convertView.getTag();
holder.icon.setImageResource(R.drawable.icon);
holder.text.setText(mData[position]);
return convertView;
}
}
在Adapter的代码中,在getView方法里首先判断convertView是否为空,若为空则加载相应布局,若不为空则直接使用该布局,这能够很有效的使用Android为listview提供的缓存机制:只加载一屏的布局,之后滑动出来的item使用的是之前已经加载的布局的缓存;
而使用ViewHoulder的目的则是节省了findViewById的时间。如果不使用ViewHolder,每次getView的时候都需要得到一次子布局,而这也是很耗时并且耗资源的;如果使用了ViewHolder作为子布局的缓存,使用View的setTag方法将缓存与每个item绑定,则也可以省去了findViewById的事件;
而将ViewHolder设置为static的目的是指在初始化Adapter时初始化一次这个内部类,否则将会在每次创建Adapter时都要初始化一次,而这是没有必要的。
上述方法能够解决大部分listview消耗资源以及卡顿的问题,但对于不同的需求的listview来说还会存在其他让listview卡顿的原因,比如listview的item每次加载时都需要获得图片并设置到imageview中,item加载时需要进行大量的计算,item里的TextView需要设置指定字体;这些耗时的操作都会让listview滑动起来很卡,带来不好的体验。
(3)getView() 中要做尽量少的事情,不要有耗时的操作。
比如item里面有图片的情况下,滑动的时候不要加载,停下来再加载。这点可以用Picasso加载,自带线程,不会造成卡顿。
@Override
public View getView(int position, View view, ViewGroup parent) {
ViewHolder holder;
if (view == null) {
view = inflater.inflate(R.layout.home_listview_item, null);
holder = new ViewHolder();
holder.food_image = (ImageView) view.findViewById(R.id.food_image);
view.setTag(holder);
}
holder = (ViewHolder) view.getTag();
Cook item = list.get(position);
Picasso.with(context).load(item.getImg()).placeholder(R.drawable.image_loding).into(holder.food_image);
return view;
}
参考:http://blog.csdn.net/jdsjlzx/article/details/45914707
- package com.zhengsonglan.listview_loading.adapter;
-
- import android.content.Context;
- import android.view.LayoutInflater;
- import android.view.View;
- import android.view.ViewGroup;
- import android.widget.BaseAdapter;
- import android.widget.ImageView;
- import android.widget.TextView;
- import com.nostra13.universalimageloader.core.ImageLoader;
- import com.zhengsonglan.listview_loading.R;
- import com.zhengsonglan.listview_loading.entity.UserEnity;
-
- import java.util.List;
-
- public class MyAdapter extends BaseAdapter {
- Context context;
- LayoutInflater inflater;
- List<UserEnity> lists;
-
- private boolean scrollState=false;
-
- public void setScrollState(boolean scrollState) {
- this.scrollState = scrollState;
- }
-
- public MyAdapter(Context context, List<UserEnity> lists) {
- this.context=context;
- this.inflater=LayoutInflater.from(context);
- this.lists=lists;
- }
-
- @Override
- public int getCount() {
- return lists==null?0:lists.size();
- }
-
- @Override
- public Object getItem(int position) {
- return lists.get(position);
- }
-
- @Override
- public long getItemId(int position) {
- return position;
- }
-
- @Override
- public View getView(int position, View convertView, ViewGroup parent) {
- ViewHolder viewHolder;
- if (convertView == null) {
- convertView=inflater.inflate(R.layout.main_item,null,true);
- viewHolder=new ViewHolder();
- viewHolder.iv_icon= (ImageView) convertView.findViewById(R.id.icon);
- viewHolder.tv_name= (TextView) convertView.findViewById(R.id.name);
- convertView.setTag(viewHolder);
- } else {
- viewHolder= (ViewHolder) convertView.getTag();
- }
- UserEnity userEnity=lists.get(position);
- String img_url=userEnity.getIcon();
- if (!scrollState){
- viewHolder.tv_name.setText(userEnity.getName());
- viewHolder.tv_name.setTag(null);
- ImageLoader.getInstance().displayImage(img_url,viewHolder.iv_icon);
- viewHolder.iv_icon.setTag("1");
- }else{
- viewHolder.tv_name.setText("加载中");
- viewHolder.tv_name.setTag(userEnity.getName());
- viewHolder.iv_icon.setTag(img_url);
- //设置默认显示图片(最好是本地资源的图片
- viewHolder.iv_icon.setImageResource(R.mipmap.ic_launcher);
- }
- return convertView;
-
- }
-
- static class ViewHolder{
- TextView tv_name;
- ImageView iv_icon;
- }
- }
我们在看看Activity中的代码,主要是监听listview的onScrollListener方法
- @Override
- public void onScrollStateChanged(AbsListView view, int scrollState) {
- switch (scrollState){
- case AbsListView.OnScrollListener.SCROLL_STATE_IDLE:
- {
-
- myAdapter.setScrollState(false);
-
- int count = view.getChildCount();
- for (int i = 0; i < count; i++) {
-
- TextView tv_name = (TextView) view.getChildAt(i).findViewById(R.id.name);
-
- ImageView iv_show= (ImageView) view.getChildAt(i).findViewById(R.id.icon);
- if (tv_name.getTag() != null) {
- tv_name.setText(tv_name.getTag().toString());
- tv_name.setTag(null);
- }
-
- if (!iv_show.getTag().equals("1")){
- String image_url=iv_show.getTag().toString();
- ImageLoader.getInstance().displayImage(image_url, iv_show);
- iv_show.setTag("1");
- }
- }
- break;
- }
- case AbsListView.OnScrollListener.SCROLL_STATE_FLING:
- {
-
- myAdapter.setScrollState(true);
- break;
- }
-
- case AbsListView.OnScrollListener.SCROLL_STATE_TOUCH_SCROLL:
- {
-
- myAdapter.setScrollState(true);
- break;
- }
- }
- }
最后记得给listview加上滑动的监听
istview.setOnScrollListener(this);
综上,listview的优化其实就是去找getView中的耗时操作,然后提取出来,要么使用异步的方式为item的布局设置数据,要是实在需要同步,就只能在Adapter初始化时将数据准备好,然后再getView中只需绑定一下就行。
- package com.zhengsonglan.listview_loading.adapter;
-
- import android.content.Context;
- import android.view.LayoutInflater;
- import android.view.View;
- import android.view.ViewGroup;
- import android.widget.BaseAdapter;
- import android.widget.ImageView;
- import android.widget.TextView;
-
- import com.nostra13.universalimageloader.core.ImageLoader;
- import com.zhengsonglan.listview_loading.R;
- import com.zhengsonglan.listview_loading.entity.UserEnity;
-
- import java.util.List;
-
-
-
-
- public class MyAdapter extends BaseAdapter {
- Context context;
- LayoutInflater inflater;
- List<UserEnity> lists;
-
- private boolean scrollState=false;
-
- public void setScrollState(boolean scrollState) {
- this.scrollState = scrollState;
- }
-
-
- public MyAdapter(Context context, List<UserEnity> lists) {
- this.context=context;
- this.inflater=LayoutInflater.from(context);
- this.lists=lists;
- }
-
- @Override
- public int getCount() {
- return lists!=null?lists.size():0;
- }
-
- @Override
- public Object getItem(int position) {
- return lists.get(position);
- }
-
- @Override
- public long getItemId(int position) {
- return position;
- }
-
- @Override
- public View getView(int position, View convertView, ViewGroup parent) {
- ViewHolder viewHolder;
- if (convertView == null) {
- convertView=inflater.inflate(R.layout.main_item,null,true);
- viewHolder=new ViewHolder();
- viewHolder.iv_icon= (ImageView) convertView.findViewById(R.id.main_item_iv_icon);
- viewHolder.tv_name= (TextView) convertView.findViewById(R.id.main_item_tv_name);
- convertView.setTag(viewHolder);
- } else {
- viewHolder= (ViewHolder) convertView.getTag();
- }
-
-
- UserEnity userEnity=lists.get(position);
-
- String img_url=userEnity.getIcon();
- if (!scrollState){
- viewHolder.tv_name.setText(userEnity.getName());
- viewHolder.tv_name.setTag(null);
- ImageLoader.getInstance().displayImage(img_url,viewHolder.iv_icon);
- viewHolder.iv_icon.setTag("1");
-
- }else{
- viewHolder.tv_name.setText("加载中");
- viewHolder.tv_name.setTag(userEnity.getName());
- viewHolder.iv_icon.setTag(img_url);
- viewHolder.iv_icon.setImageResource(R.mipmap.ic_launcher);
-
- }
- return convertView;
-
- }
-
- static class ViewHolder{
- TextView tv_name;
- ImageView iv_icon;
- }
- }
这个Adapter中重要的代码如下:
-
- private boolean scrollState=false;
- public void setScrollState(boolean scrollState) {
- this.scrollState = scrollState;
- }
-
- UserEnity userEnity=lists.get(position);
-
- if (!scrollState){
-
- viewHolder.tv_name.setText(userEnity.getName());
-
- viewHolder.tv_name.setTag(null);
-
- ImageLoader.getInstance().displayImage(img_url,viewHolder.iv_icon);
-
- viewHolder.iv_icon.setTag("1");
-
- }else{
- viewHolder.tv_name.setText("加载中");
-
- viewHolder.tv_name.setTag(userEnity.getName());
-
- viewHolder.iv_icon.setTag(img_url);
-
- viewHolder.iv_icon.setImageResource(R.mipmap.ic_launcher);
-
- }
我们在看看Activity中的代码,主要是监听listview的onscrolllistener方法
- @Override
- public void onScrollStateChanged(AbsListView view, int scrollState) {
- switch (scrollState){
-
- case AbsListView.OnScrollListener.SCROLL_STATE_IDLE:
- {
-
- myAdapter.setScrollState(false);
-
- int count = view.getChildCount();
- Log.e("MainActivity",count+"");
-
- for (int i = 0; i < count; i++) {
-
- TextView tv_name = (TextView) view.getChildAt(i).findViewById(R.id.main_item_tv_name);
-
- ImageView iv_show= (ImageView) view.getChildAt(i).findViewById(R.id.main_item_iv_icon);
-
- if (tv_name.getTag() != null) {
- tv_name.setText(tv_name.getTag().toString());
- tv_name.setTag(null);
- }
-
- if (!iv_show.getTag().equals("1")){
- String image_url=iv_show.getTag().toString();
- ImageLoader.getInstance().displayImage(image_url, iv_show);
- iv_show.setTag("1");
- }
- }
- break;
- }
- case AbsListView.OnScrollListener.SCROLL_STATE_FLING:
- {
-
- myAdapter.setScrollState(true);
- break;
- }
-
- case AbsListView.OnScrollListener.SCROLL_STATE_TOUCH_SCROLL:
- {
-
- myAdapter.setScrollState(true);
- break;
- }
- }
- }
最后记得给listview加上滑动的监听
listview.setOnScrollListener(this);
效果