ListView 如何提高其效率?简单的说,就是如何优化ListView。
可以从以下方面考虑一下:
① 复用ConvertView
② 自定义静态类ViewHolder③ 使用分页加载
④ 使用WeakRefrence 、SoftReference引用ImageView 对象
一般,我们用ListView时都会用到适配器(Adapter),一般的Adapter都是以下的这种形式的:
public class MyAdapter extends BaseAdapter {
private Context mContext;
private List<SongInfo> songlists;
private SongInfo songInfo;
private ImageView albumImage; // 专辑图片
private TextView musicTitle; // 音乐标题
private TextView musicDuration; // 音乐时长
private TextView musicArtist; // 音乐艺术家
@Override
public int getCount() {
return songlists.size();
}
@Override
public Object getItem(int position) {
return songlists.get(position);
}
@Override
public long getItemId(int position) {
return position;
}
@Override
public View getView(int position, View convertView, ViewGroup parent) {
View view = LayoutInflater.from(mContext).inflate(
R.layout.audio_list_item_layout, null);
albumImage = (ImageView) view.findViewById(R.id.albumImage);
musicTitle = (TextView) view.findViewById(R.id.music_title);
musicArtist = (TextView) view.findViewById(R.id.music_Artist);
musicDuration = (TextView) view.findViewById(R.id.music_duration);
songInfo = songlists.get(position);
// 设置默认图片
albumImage.setImageResource(R.drawable.album);
musicTitle.setText(songInfo.getSongName()); // 显示标题
musicArtist.setText(songInfo.getSinger()); // 显示艺术家
musicDuration.setText(Utils.formatTime(songInfo.getDuration()));// 显示时长
return view;
}
}
① 复用ConvertView
将getView的方法改成下面的这种形式
@Override
public View getView(int position, View convertView, ViewGroup parent) {
songInfo = songlists.get(position);
View view;
if (convertView == null) {
view = LayoutInflater.from(mContext).inflate(
R.layout.audio_list_item_layout, null);
}else{
view=convertView;
}
albumImage = (ImageView) view.findViewById(R.id.albumImage);
musicTitle = (TextView) view.findViewById(R.id.music_title);
musicArtist = (TextView) view.findViewById(R.id.music_Artist);
musicDuration = (TextView) view.findViewById(R.id.music_duration);
// 设置默认图片
albumImage.setImageResource(R.drawable.album);
musicTitle.setText(songInfo.getSongName()); // 显示标题
musicArtist.setText(songInfo.getSinger()); // 显示艺术家
musicDuration.setText(Utils.formatTime(songInfo.getDuration()));// 显示时长
return view;
}
convertView是历史View对象的缓存,也就是在拖动ListView时,最顶部的Item对象拖出了手机界面后变成的被回收掉的iew对象。当ListView最底部的Item对象进入界面时,就可以使用刚才最顶部被回收掉的iew对象,这样就达到了复用convertView的效果了。
② 自定义静态类ViewHolder
在上面的① 复用ConvertView方法内,我们还看到findViewById的方法,由于每一次显示一个Item对象时都会调用getView方法,若在ListView
中有成千上万个Item对象,在拖动ListView显示Item时就会多次调用getView方法,也就是会多次调用findViewById方法,这也会导致ListView可能还是
会有一点点的卡顿现象,这时就可以使用② 自定义静态类ViewHolder来优化ListView了。
步骤:
将Item中需要用到的对象放在一个容器中,并将这个容器定义成静态的,这样在类创建的时候就创建了容器,并且只创建一次,后面要使用时就可以直接使用
而不用再创建了。如:
public static class ViewHolder {
// 所有控件对象引用
public ImageView albumImage; // 专辑图片
public TextView musicTitle; // 音乐标题
public TextView musicDuration; // 音乐时长
public TextView musicArtist; // 音乐艺术家
}
这里设置ViewHolder为static,也就是静态的,静态类只会在第一次加载时会耗费比较长时间,但是后面就可以很好帮助加载,同时保证了内存中只有一个ViewHolder,节省了内存的开销。getView的方法改成下面:
public View getView(int position, View convertView, ViewGroup parent) {
ViewHolder viewHolder = null;
if (convertView == null) {
viewHolder = new ViewHolder();
convertView = LayoutInflater.from(mContext).inflate(
R.layout.audio_list_item_layout, null);
viewHolder.albumImage = (ImageView) convertView
.findViewById(R.id.albumImage);
viewHolder.musicTitle = (TextView) convertView
.findViewById(R.id.music_title);
viewHolder.musicArtist = (TextView) convertView
.findViewById(R.id.music_Artist);
viewHolder.musicDuration = (TextView) convertView
.findViewById(R.id.music_duration);
convertView.setTag(viewHolder); // 表示给View添加一个格外的数据,
} else {
viewHolder = (ViewHolder) convertView.getTag();// 通过getTag的方法将数据取出来
}
songInfo = songlists.get(position);
// 设置默认图片
viewHolder.albumImage.setImageResource(R.drawable.album);
viewHolder.musicTitle.setText(songInfo.getSongName()); // 显示标题
viewHolder.musicArtist.setText(songInfo.getSinger()); // 显示艺术家
viewHolder.musicDuration.setText(Utils.formatTime(songInfo
.getDuration()));// 显示时长
return convertView;
}
前面的两种方法是针对android的 内部机制对ListView作出的优化策略,后面的两种方法是视项目的情况而使用的。
③ 使用分页加载
当我们需要显示很多数据条目时,若一下子就显示过多的数据就会造成界面很卡,甚至会卡死。因此在数据很多的时候采用分页加载是一个不错的选择,
采用分页加载可以是界面更加顺畅。
下面通过一个例子说明一下分页加载的用法:
界面代码
list_page.xml
<?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/lv_id"
android:layout_width="fill_parent"
android:layout_height="wrap_content" />
</LinearLayout>
list_page_item.xml
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="horizontal" >
<TextView
android:id="@+id/title_id"
android:text="title"
android:layout_width="wrap_content"
android:layout_height="30dp"/>
<TextView
android:id="@+id/tv_id"
android:layout_marginLeft="50dp"
android:text="contenttext"
android:layout_width="wrap_content"
android:layout_height="30dp"/>
</LinearLayout>
list_page_load.xml
<?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:gravity="center_horizontal"
android:orientation="vertical"
android:paddingBottom="13dp"
android:paddingTop="13dp" >
<Button
android:id="@+id/more_id"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:gravity="center_horizontal"
android:text="查看更多..."
android:textSize="20sp" />
<LinearLayout
android:id="@+id/load_id"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:orientation="horizontal"
android:visibility="gone" >
<ProgressBar
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_gravity="center_horizontal" />
<TextView
android:layout_width="wrap_content"
android:layout_height="fill_parent"
android:layout_marginLeft="10dp"
android:gravity="center_vertical"
android:text="正在加载..."
android:textSize="20dp" />
</LinearLayout>
</LinearLayout>
java代码部分
MainActivity.java
public class MainActivity extends Activity {
private ListView listView;
private List<Map<String,Object>> data;
private listViewAdapter adapter;
//分页加载的数据的数量
private int pageSize=10;
private final int pageType=1;
//查看更多
private TextView moreTextView;
//正在加载进度条
private LinearLayout loadProgressBar;
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.list_page);
listView=(ListView)findViewById(R.id.lv_id);
//第一个参数:1起始数 第二个参数:显示的数目
data=InitValue.initValue(1,15);
//在ListView中添加"加载更多"
addPageMore();
//添加"加载更多"一定要在设置Adapter之前
adapter=new listViewAdapter();
listView.setAdapter(adapter);
}
private class listViewAdapter extends BaseAdapter{
int count=data.size();
@Override
public int getCount() {
return count;
}
@Override
public Object getItem(int position) {
return position;
}
@Override
public long getItemId(int position) {
return position;
}
@Override
public View getView(int position, View convertView, ViewGroup parent) {
View view=LayoutInflater.from(MainActivity.this).inflate(R.layout.list_page_item, null);
TextView title=(TextView)view.findViewById(R.id.tv_id);
TextView text=(TextView)view.findViewById(R.id.title_id);
title.setText(data.get(position).get("title").toString());
text.setText(data.get(position).get("text").toString());
return view;
}
}
/**
* 加载下一页的数据
* @param pageStart
* @param pageSize
*/
private void chageListView(int pageStart,int pageSize){
List<Map<String,Object>> data=InitValue.initValue(pageStart,pageSize);
for (Map<String, Object> map : data) {
this.data.add(map);
}
data=null;
}
/**
* 在ListView中添加"加载更多"
*/
private void addPageMore(){
View view=LayoutInflater.from(this).inflate(R.layout.list_page_load, null);
moreTextView=(TextView)view.findViewById(R.id.more_id);
loadProgressBar=(LinearLayout)view.findViewById(R.id.load_id);
moreTextView.setOnClickListener(new Button.OnClickListener() {
@Override
public void onClick(View v) {
//隐藏"加载更多"
moreTextView.setVisibility(View.GONE);
//显示进度条
loadProgressBar.setVisibility(View.VISIBLE);
new Thread(new Runnable() {
@Override
public void run() {
//休眠3秒,用于模拟网络操作时间
try {
Thread.sleep(3000);
} catch (InterruptedException e) {
e.printStackTrace();
}
//加载模拟数据:下一页数据, 在正常情况下,上面的休眠是不需要,直接使用下面这句代码加载相关数据
chageListView(data.size(),pageSize);
Message mes=handler.obtainMessage(pageType);
handler.sendMessage(mes);
}
}).start();
}
});
listView.addFooterView(view);
}
private Handler handler=new Handler(){
@Override
public void handleMessage(Message msg) {
switch (msg.what) {
case pageType:
//改变适配器的数目
adapter.count += pageSize;
//通知适配器,发现改变操作
adapter.notifyDataSetChanged();
//再次显示"加载更多"
moreTextView.setVisibility(View.VISIBLE);
//再次隐藏“进度条”
loadProgressBar.setVisibility(View.GONE);
break;
default:
break;
}
super.handleMessage(msg);
}
};
}
InitValue.java
public class InitValue {
public static int page=1;
/**
* 模拟数据分页加载,
* @param pageStart 起始数
* @param pageSize 每页显示数目
* @return
*/
public static List<Map<String,Object>> initValue(int pageStart,int pageSize){
Map<String,Object> map;
List<Map<String,Object>> list=new ArrayList<Map<String,Object>>();
for(int i=0;i<pageSize;i++){
map=new HashMap<String,Object>();
map.put("text", " context...");
map.put("title", " "+page+"_ListView分页显示");
++page;
list.add(map);
}
return list;
}
}
实现效果
④ 使用WeakRefrence 、SoftReference引用ImageView 对象
在ListView中取图片时也不要直接拿个路径去取图片,而是以WeakReference(使用WeakReference代替强引用。比如可以使用WeakReference mContextRef)、SoftReference、WeakHashMap等的来存储图片信息。
例子:
package com.lvguo.scanstreet.activity;
import java.io.File;
import java.lang.ref.SoftReference;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import android.app.Activity;
import android.app.AlertDialog;
import android.content.Context;
import android.content.DialogInterface;
import android.content.Intent;
import android.content.res.TypedArray;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.graphics.BitmapFactory.Options;
import android.os.Bundle;
import android.view.View;
import android.view.ViewGroup;
import android.view.WindowManager;
import android.widget.AdapterView;
import android.widget.AdapterView.OnItemLongClickListener;
import android.widget.BaseAdapter;
import android.widget.Gallery;
import android.widget.ImageView;
import android.widget.Toast;
import com.lvguo.scanstreet.R;
import com.lvguo.scanstreet.data.ApplicationData;
/**
* @Title: PhotoScanActivity.java
* @Description: 照片预览控制类
* @author XiaoMa
*/
public class PhotoScanActivity extends Activity {
private Gallery gallery ;
private List<String> ImageList;
private List<String> it ;
private ImageAdapter adapter ;
private String path ;
private String shopType;
private HashMap<String, SoftReference<Bitmap>> imageCache = null;
private Bitmap bitmap = null;
private SoftReference<Bitmap> srf = null;
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
getWindow().setFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN,
WindowManager.LayoutParams.FLAG_FULLSCREEN);
setContentView(R.layout.photoscan);
Intent intent = this.getIntent();
if(intent != null){
if(intent.getBundleExtra(“bundle”) != null){
Bundle bundle = intent.getBundleExtra(“bundle”);
path = bundle.getString(“path”);
shopType = bundle.getString(“shopType”);
}
}
init();
}
private void init(){
imageCache = new HashMap<String, SoftReference<Bitmap>>();
gallery = (Gallery)findViewById(R.id.gallery);
ImageList = getSD();
if(ImageList.size() == 0){
Toast.makeText(getApplicationContext(), “无照片,请返回拍照后再使用预览”, Toast.LENGTH_SHORT).show();
return ;
}
adapter = new ImageAdapter(this, ImageList);
gallery.setAdapter(adapter);
gallery.setOnItemLongClickListener(longlistener);
}
/**
* Gallery长按事件操作实现
*/
private OnItemLongClickListener longlistener = new OnItemLongClickListener() {
@Override
public boolean onItemLongClick(AdapterView<?> parent, View view,
final int position, long id) {
//此处添加长按事件删除照片实现www.2cto.com
AlertDialog.Builder dialog = new AlertDialog.Builder(PhotoScanActivity.this);
dialog.setIcon(R.drawable.warn);
dialog.setTitle(“删除提示”);
dialog.setMessage(“你确定要删除这张照片吗?”);
dialog.setPositiveButton(“确定”, new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int which) {
File file = new File(it.get(position));
boolean isSuccess;
if(file.exists()){
isSuccess = file.delete();
if(isSuccess){
ImageList.remove(position);
adapter.notifyDataSetChanged();
//gallery.setAdapter(adapter);
if(ImageList.size() == 0){
Toast.makeText(getApplicationContext(), getResources().getString(R.string.phoSizeZero), Toast.LENGTH_SHORT).show();
}
Toast.makeText(getApplicationContext(), getResources().getString(R.string.phoDelSuccess), Toast.LENGTH_SHORT).show();
}
}
}
});
dialog.setNegativeButton(“取消”,new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int which) {
dialog.dismiss();
}
});
dialog.create().show();
return false;
}
};
/**
* 获取SD卡上的所有图片文件
* @return
*/
private List<String> getSD() {
/* 设定目前所在路径 */
File fileK ;
it = new ArrayList<String>();
if(“newadd”.equals(shopType)){
//如果是从查看本人新增列表项或商户列表项进来时
fileK = new File(ApplicationData.TEMP);
}else{
//此时为纯粹新增
fileK = new File(path);
}
File[] files = fileK.listFiles();
if(files != null && files.length>0){
for(File f : files ){
if(getImageFile(f.getName())){
it.add(f.getPath());
Options bitmapFactoryOptions = new BitmapFactory.Options();
//下面这个设置是将图片边界不可调节变为可调节
bitmapFactoryOptions.inJustDecodeBounds = true;
bitmapFactoryOptions.inSampleSize = 5;
int outWidth = bitmapFactoryOptions.outWidth;
int outHeight = bitmapFactoryOptions.outHeight;
float imagew = 150;
float imageh = 150;
int yRatio = (int) Math.ceil(bitmapFactoryOptions.outHeight
/ imageh);
int xRatio = (int) Math
.ceil(bitmapFactoryOptions.outWidth / imagew);
if (yRatio > 1 || xRatio > 1) {
if (yRatio > xRatio) {
bitmapFactoryOptions.inSampleSize = yRatio;
} else {
bitmapFactoryOptions.inSampleSize = xRatio;
}
}
bitmapFactoryOptions.inJustDecodeBounds = false;
bitmap = BitmapFactory.decodeFile(f.getPath(),
bitmapFactoryOptions);
//bitmap = BitmapFactory.decodeFile(f.getPath());
srf = new SoftReference<Bitmap>(bitmap);
imageCache.put(f.getName(), srf);
}
}
}
return it;
}
/**
* 获取图片文件方法的具体实现
* @param fName
* @return
*/
private boolean getImageFile(String fName) {
boolean re;
/* 取得扩展名 */
String end = fName
.substring(fName.lastIndexOf(“.”) + 1, fName.length())
.toLowerCase();
/* 按扩展名的类型决定MimeType */
if (end.equals(“jpg”) || end.equals(“gif”) || end.equals(“png”)
|| end.equals(“jpeg”) || end.equals(“bmp”)) {
re = true;
} else {
re = false;
}
return re;
}
public class ImageAdapter extends BaseAdapter{
/* 声明变量 */
int mGalleryItemBackground;
private Context mContext;
private List<String> lis;
/* ImageAdapter的构造符 */
public ImageAdapter(Context c, List<String> li) {
mContext = c;
lis = li;
TypedArray a = obtainStyledAttributes(R.styleable.Gallery);
mGalleryItemBackground = a.getResourceId(R.styleable.Gallery_android_galleryItemBackground, 0);
a.recycle();
}
/* 几定要重写的方法getCount,传回图片数目 */
public int getCount() {
return lis.size();
}
/* 一定要重写的方法getItem,传回position */
public Object getItem(int position) {
return lis.get(position);
}
/* 一定要重写的方法getItemId,传并position */
public long getItemId(int position) {
return position;
}
/* 几定要重写的方法getView,传并几View对象 */
public View getView(int position, View convertView, ViewGroup parent) {
System.out.println(“lis:”+lis);
File file = new File(it.get(position));
SoftReference<Bitmap> srf = imageCache.get(file.getName());
Bitmap bit = srf.get();
ImageView i = new ImageView(mContext);
i.setImageBitmap(bit);
i.setScaleType(ImageView.ScaleType.FIT_XY);
i.setLayoutParams( new Gallery.LayoutParams(WindowManager.LayoutParams.WRAP_CONTENT,
WindowManager.LayoutParams.WRAP_CONTENT));
return i;
}
}
}