最簡單也是最複雜的控件——ListView,這句話真的一點也不誇張。依稀記得大三下學期,抱着一本《Android第一行代碼》,每天開開心心的學一點基礎知識。UI學了沒多少,就接觸到了ListView。用個for循環,給ListView塞一串item,自己還可以滑,(@ο@) 哇~,這是極好的。
又扯遠了,上乾貨。
假設一個界面就一個ListView,然後ListView的Item就是一個ImageView,額,擼代碼:
activity_main.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">
<ListView
android:id="@+id/list_view"
android:layout_width="match_parent"
android:layout_height="match_parent" />
</LinearLayout>
image_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">
<ImageView
android:id="@+id/image"
android:layout_width="match_parent"
android:layout_height="150dp"
android:src="@mipmap/ic_launcher" />
</LinearLayout>
然後要做的就是從網絡上獲取圖片放在ListView中顯示,有人說這不簡單,直接異步加載不就收工了麼,先貼出來ImageAdapter的代碼:
package com.cjt_pc.listviewtest;
/**
* Created by cjt-pc on 2015/10/13.
* Email:879309896@qq.com
*/
import android.content.Context;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.os.AsyncTask;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.ArrayAdapter;
import android.widget.ImageView;
import java.io.IOException;
import java.io.InputStream;
import java.net.URL;
public class ImageAdapter extends ArrayAdapter<String> {
private int resource;
public ImageAdapter(Context context, int resource, String[] objects) {
super(context, resource, objects);
this.resource = resource;
}
@Override
public View getView(int position, View convertView, ViewGroup parent) {
String url = getItem(position);
ViewHolder viewHolder;
if (convertView == null) {
convertView = LayoutInflater.from(getContext()).inflate(resource, null);
viewHolder = new ViewHolder();
viewHolder.imageView = (ImageView) convertView.findViewById(R.id.image);
convertView.setTag(viewHolder);
} else {
viewHolder = (ViewHolder) convertView.getTag();
}
new BitmapTask(viewHolder.imageView).execute(url);
return convertView;
}
class ViewHolder {
ImageView imageView;
}
/**
* 異步下載圖片的任務。
*
* @author cjt-pc
*/
class BitmapTask extends AsyncTask<String, Void, Bitmap> {
private ImageView imageView;
public BitmapTask(ImageView imageView) {
this.imageView = imageView;
}
@Override
protected Bitmap doInBackground(String... strings) {
String url = strings[0];
InputStream is = null;
Bitmap bitmap = null;
try {
is = new URL(url).openStream();
// 注意這裏沒有直接用decodeStream,是因爲由於本身的原因這樣極其不穩定
byte[] bytes = PictureCompressUtil.readStream(is);
bitmap = BitmapFactory.decodeByteArray(bytes, 0, bytes.length);
} catch (Exception e) {
e.printStackTrace();
return null;
} finally {
try {
if (is != null) {
is.close();
}
} catch (IOException e) {
e.printStackTrace();
}
}
return bitmap;
}
@Override
protected void onPostExecute(Bitmap bitmap) {
imageView.setImageBitmap(bitmap);
}
}
}
圖片地址的封裝類:
package com.cjt_pc.listviewtest;
/**
* Created by cjt-pc on 2015/10/13.
* Email:[email protected]
*/
public class Images {
public final static String[] imageUrls = new String[] {
"http://b.hiphotos.baidu.com/zhidao/pic/item/241f95cad1c8a7862039afc56609c93d71cf50ee.jpg",
"http://d.hiphotos.baidu.com/zhidao/pic/item/09fa513d269759ee5eb29a00b3fb43166c22df1f.jpg",
"http://h.hiphotos.baidu.com/zhidao/pic/item/fd039245d688d43f78756b397c1ed21b0ff43b19.jpg",
"http://e.hiphotos.baidu.com/zhidao/pic/item/30adcbef76094b3608b1fe26a2cc7cd98c109dde.jpg",
"http://a.hiphotos.baidu.com/zhidao/pic/item/42a98226cffc1e17bae869fa4b90f603728de9a2.jpg",
"http://f.hiphotos.baidu.com/zhidao/pic/item/b7003af33a87e950b86616a711385343faf2b48c.jpg",
"http://a.hiphotos.baidu.com/zhidao/pic/item/9922720e0cf3d7ca0b84dabdf31fbe096a63a98f.jpg",
"http://c.hiphotos.baidu.com/zhidao/pic/item/838ba61ea8d3fd1fa30f47f1314e251f94ca5ff3.jpg",
"http://a.hiphotos.baidu.com/zhidao/pic/item/d4628535e5dde71128bea63da6efce1b9c166189.jpg",
"http://f.hiphotos.baidu.com/zhidao/pic/item/e7cd7b899e510fb31a77925cd833c895d0430c8a.jpg",
"http://www.lipu.net/Photo_Max/200981321538.jpg",
"http://t1.niutuku.com/960/58/58-421244.jpg",
"http://a2.att.hudong.com/32/06/01300542212415138174064802766.jpg",
"http://static.bbs.sgnet.cc/attachment/forum/201211/17/110951koommdjbo7rbrr5r.jpg",
"https://img-my.csdn.net/uploads/201508/05/1438760758_3497.jpg",
"https://img-my.csdn.net/uploads/201508/05/1438760758_6667.jpg",
"https://img-my.csdn.net/uploads/201508/05/1438760757_3588.jpg",
"https://img-my.csdn.net/uploads/201508/05/1438760756_3304.jpg",
"https://img-my.csdn.net/uploads/201508/05/1438760755_6715.jpeg",
"https://img-my.csdn.net/uploads/201508/05/1438760726_5120.jpg",
"https://img-my.csdn.net/uploads/201508/05/1438760726_8364.jpg",
"https://img-my.csdn.net/uploads/201508/05/1438760725_4031.jpg",
"https://img-my.csdn.net/uploads/201508/05/1438760724_9463.jpg",
"https://img-my.csdn.net/uploads/201508/05/1438760724_2371.jpg",
"https://img-my.csdn.net/uploads/201508/05/1438760707_4653.jpg",
"https://img-my.csdn.net/uploads/201508/05/1438760706_6864.jpg",
"https://img-my.csdn.net/uploads/201508/05/1438760706_9279.jpg",
"https://img-my.csdn.net/uploads/201508/05/1438760704_2341.jpg",
"https://img-my.csdn.net/uploads/201508/05/1438760704_5707.jpg",
"https://img-my.csdn.net/uploads/201508/05/1438760685_5091.jpg",
"https://img-my.csdn.net/uploads/201508/05/1438760685_4444.jpg",
"https://img-my.csdn.net/uploads/201508/05/1438760684_8827.jpg",
"https://img-my.csdn.net/uploads/201508/05/1438760683_3691.jpg",
"https://img-my.csdn.net/uploads/201508/05/1438760683_7315.jpg",
"https://img-my.csdn.net/uploads/201508/05/1438760663_7318.jpg",
"https://img-my.csdn.net/uploads/201508/05/1438760662_3454.jpg",
"https://img-my.csdn.net/uploads/201508/05/1438760662_5113.jpg",
"https://img-my.csdn.net/uploads/201508/05/1438760661_3305.jpg",
"https://img-my.csdn.net/uploads/201508/05/1438760661_7416.jpg",
"https://img-my.csdn.net/uploads/201508/05/1438760589_2946.jpg",
"https://img-my.csdn.net/uploads/201508/05/1438760589_1100.jpg",
"https://img-my.csdn.net/uploads/201508/05/1438760588_8297.jpg",
"https://img-my.csdn.net/uploads/201508/05/1438760587_2575.jpg",
"https://img-my.csdn.net/uploads/201508/05/1438760587_8906.jpg",
"https://img-my.csdn.net/uploads/201508/05/1438760550_2875.jpg",
"https://img-my.csdn.net/uploads/201508/05/1438760550_9517.jpg",
"https://img-my.csdn.net/uploads/201508/05/1438760549_7093.jpg",
"https://img-my.csdn.net/uploads/201508/05/1438760549_1352.jpg",
"https://img-my.csdn.net/uploads/201508/05/1438760548_2780.jpg",
"https://img-my.csdn.net/uploads/201508/05/1438760531_1776.jpg",
"https://img-my.csdn.net/uploads/201508/05/1438760531_1380.jpg",
"https://img-my.csdn.net/uploads/201508/05/1438760530_4944.jpg",
"https://img-my.csdn.net/uploads/201508/05/1438760530_5750.jpg",
"https://img-my.csdn.net/uploads/201508/05/1438760529_3289.jpg",
"https://img-my.csdn.net/uploads/201508/05/1438760500_7871.jpg",
"https://img-my.csdn.net/uploads/201508/05/1438760500_6063.jpg",
"https://img-my.csdn.net/uploads/201508/05/1438760499_6304.jpeg",
"https://img-my.csdn.net/uploads/201508/05/1438760499_5081.jpg",
"https://img-my.csdn.net/uploads/201508/05/1438760498_7007.jpg",
"https://img-my.csdn.net/uploads/201508/05/1438760478_3128.jpg",
"https://img-my.csdn.net/uploads/201508/05/1438760478_6766.jpg",
"https://img-my.csdn.net/uploads/201508/05/1438760477_1358.jpg",
"https://img-my.csdn.net/uploads/201508/05/1438760477_3540.jpg",
"https://img-my.csdn.net/uploads/201508/05/1438760476_1240.jpg",
"https://img-my.csdn.net/uploads/201508/05/1438760446_7993.jpg",
"https://img-my.csdn.net/uploads/201508/05/1438760446_3641.jpg",
"https://img-my.csdn.net/uploads/201508/05/1438760445_3283.jpg",
"https://img-my.csdn.net/uploads/201508/05/1438760444_8623.jpg",
"https://img-my.csdn.net/uploads/201508/05/1438760444_6822.jpg",
"https://img-my.csdn.net/uploads/201508/05/1438760422_2224.jpg",
"https://img-my.csdn.net/uploads/201508/05/1438760421_2824.jpg",
"https://img-my.csdn.net/uploads/201508/05/1438760420_2660.jpg",
"https://img-my.csdn.net/uploads/201508/05/1438760420_7188.jpg",
"https://img-my.csdn.net/uploads/201508/05/1438760419_4123.jpg"
};
}
額,主Activity代碼就簡單了:
package com.cjt_pc.listviewtest;
import android.app.Activity;
import android.os.Bundle;
import android.widget.ListView;
public class MainActivity extends Activity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
ListView listView = (ListView) findViewById(R.id.list_view);
ImageAdapter adapter = new ImageAdapter(this, R.layout.image_item, Images.imageUrls);
listView.setAdapter(adapter);
}
}
是的,沒有什麼難度,但是運行之後就會直接閃退,OOM,何爲OOM?out of memory!!內存溢出!!直觀點說就是圖片太大了,手機內存不夠!!也是,用瀏覽器看看這些地址的圖片,都是我精挑細選的,超大超級大有木有!!這樣我們就遇到了第一個問題,OOM!!
仔細想想,一張3000*2000的圖片,要放在300*200的ImageView中顯示,直接加載有意義嗎?答案當然是NO。這個時候我們就要想到壓縮了,怎麼壓,藉助Options這個屬性,先看看這個工具類:
package com.cjt_pc.listviewtest;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import java.io.ByteArrayOutputStream;
import java.io.InputStream;
/**
* Created by cjt-pc on 2015/10/13.
* Email:[email protected]
*/
public class PictureCompressUtil {
/**
* 獲取適當的壓縮比率
*
* @param options 解析參賽
* @param tarWidth 目標寬度
* @param tarHeight 目標高度
* @return 適當的InSampleSize
*/
public static int calculateInSampleSize(BitmapFactory.Options options, int tarWidth, int tarHeight) {
// 獲取源圖片的實際寬度和高度
final int realWidth = options.outWidth;
final int realHeight = options.outHeight;
int inSampleSize = 1;
if (realHeight > tarHeight || realWidth > tarWidth) {
// 計算出實際寬高和目標寬高的比率
final int heightRatio = Math.round((float) realHeight / (float) tarHeight);
final int widthRatio = Math.round((float) realWidth / (float) tarWidth);
// 注意:網上郭大神說去較小的那一個,實測選較小的那一個圖片大了多了滑動起來會比較卡,選較大的那一個完美解決
inSampleSize = heightRatio > widthRatio ? heightRatio : widthRatio;
}
return inSampleSize;
}
public static Bitmap decodeSampledBitmapFromByteArray(byte[] bytes, int tarWidth, int tarHeight) {
// 第一次解析將inJustDecodeBounds設置爲true,來獲取圖片大小
final BitmapFactory.Options options = new BitmapFactory.Options();
options.inJustDecodeBounds = true;
BitmapFactory.decodeByteArray(bytes, 0, bytes.length, options);
// 調用上面定義的方法計算inSampleSize值
options.inSampleSize = calculateInSampleSize(options, tarWidth, tarHeight);
// 使用獲取到的inSampleSize值再次解析圖片
options.inJustDecodeBounds = false;
return BitmapFactory.decodeByteArray(bytes, 0, bytes.length, options);
}
public static byte[] readStream(InputStream inStream) throws Exception {
ByteArrayOutputStream outStream = new ByteArrayOutputStream();
byte[] buffer = new byte[1024];
int len;
while ((len = inStream.read(buffer)) != -1) {
outStream.write(buffer, 0, len);
}
outStream.close();
inStream.close();
return outStream.toByteArray();
}
}
關於壓縮就不細講了,不是本篇的重點,代碼也不是太難懂,需要指出的是,先設置options.inJustDecodeBounds爲true,代表只解析邊界,得到源圖片的寬度和高度,然後根據需要需要放置的目標ImageView的大小計算一個適當的壓縮比率,然後將options.inJustDecodeBounds設置爲false即可完成解析圖片,最後返回一個Bitmap對象。
修改哈ImageAdapter中部分代碼:
package com.cjt_pc.listviewtest;
/**
* Created by cjt-pc on 2015/10/13.
* Email:879309896@qq.com
*/
import android.content.Context;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.os.AsyncTask;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.ArrayAdapter;
import android.widget.ImageView;
import java.io.IOException;
import java.io.InputStream;
import java.net.URL;
public class ImageAdapter extends ArrayAdapter<String> {
...
/**
* 異步下載圖片的任務。
*
* @author cjt-pc
*/
class BitmapTask extends AsyncTask<String, Void, Bitmap> {
...
is = new URL(url).openStream();
// 注意這裏沒有直接用decodeStream,是因爲由於本身的原因這樣極其不穩定
byte[] bytes = PictureCompressUtil.readStream(is);
// bitmap = BitmapFactory.decodeByteArray(bytes, 0, bytes.length);
bitmap = PictureCompressUtil.decodeSampledBitmapFromByteArray(bytes, 1440, 300);
...
}
}
改動的地方非常少,只是把解析byte數組的方法封裝成了一個工具類,然後再運行看看,沒有閃退了出現OOM了吧,但是懊惱的是,爲什麼圖片好亂,感覺有些圖被加載了幾遍,而且出現在了不該出現的位置。
是的,基於ListView的工作原理,離開屏幕的view轉爲convertView,能夠重用提高效率,但試想,離開的view已經加載好的圖片,然後在下一個重用該view的item上初始化的時候就會顯示上一張圖片,然後在異步加載圖片完成後會顯示本該顯示的圖片,這樣就出現了閃屏、亂序的樣子。
怎麼解決呢?看看一般情況下采用ViewHolder提高效率時,是採用的convertView.setTag(viewHolder)形式,然後讀的時候用viewHolder = (ViewHolder) convertView.getTag(),這樣做的好處就是不用每次都用findViewById。
ImageView繼承自View,當然也具有setTag(Object object)方法,一般情況下要是tag唯一,就設置成圖片的url地址。那麼怎麼拿到綁定的ImageView呢?其實,除了findViewById之外,還有一個findViewByTag方法,這樣思路就屢清楚了。
看看改進後的ImageAdapter源代碼:
package com.cjt_pc.listviewtest;
/**
* Created by cjt-pc on 2015/10/13.
* Email:879309896@qq.com
*/
import android.content.Context;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.os.AsyncTask;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.ArrayAdapter;
import android.widget.ImageView;
import android.widget.ListView;
import java.io.IOException;
import java.io.InputStream;
import java.net.URL;
public class ImageAdapter extends ArrayAdapter<String> {
private int resource;
private ListView mListView;
public ImageAdapter(Context context, int resource, String[] objects) {
super(context, resource, objects);
this.resource = resource;
}
@Override
public View getView(int position, View convertView, ViewGroup parent) {
mListView = (ListView) parent;
String url = getItem(position);
ViewHolder viewHolder;
if (convertView == null) {
convertView = LayoutInflater.from(getContext()).inflate(resource, null);
viewHolder = new ViewHolder();
viewHolder.imageView = (ImageView) convertView.findViewById(R.id.image);
convertView.setTag(viewHolder);
} else {
viewHolder = (ViewHolder) convertView.getTag();
}
viewHolder.imageView.setTag(url);
viewHolder.imageView.setImageResource(R.mipmap.ic_launcher);
new BitmapTask().execute(url);
return convertView;
}
class ViewHolder {
ImageView imageView;
}
/**
* 異步下載圖片的任務。
*
* @author cjt-pc
*/
class BitmapTask extends AsyncTask<String, Void, Bitmap> {
private String url;
@Override
protected Bitmap doInBackground(String... strings) {
url = strings[0];
InputStream is = null;
Bitmap bitmap = null;
try {
is = new URL(url).openStream();
// 注意這裏沒有直接用decodeStream,是因爲由於本身的原因這樣極其不穩定
byte[] bytes = PictureCompressUtil.readStream(is);
// bitmap = BitmapFactory.decodeByteArray(bytes, 0, bytes.length);
bitmap = PictureCompressUtil.decodeSampledBitmapFromByteArray(bytes, 1440, 300);
} catch (Exception e) {
e.printStackTrace();
return null;
} finally {
try {
if (is != null) {
is.close();
}
} catch (IOException e) {
e.printStackTrace();
}
}
return bitmap;
}
@Override
protected void onPostExecute(Bitmap bitmap) {
ImageView imageView = (ImageView) mListView.findViewWithTag(url);
if (imageView != null) {
imageView.setImageBitmap(bitmap);
}
}
}
}
這裏顯示定義了一個全局變量mListView,然後在getView中初始化,修改BitmapTask的構造方法,不需傳入ImageView,直接在異步加載完成後onPostExecute中拿到ImageView:ImageView imageView = (ImageView) mListView.findViewWithTag(url);如果得到的不爲空,說明該item還沒有離開屏幕,即刻給ImageView賦值。
還有,在getView方法中,加了兩句話:viewHolder.imageView.setTag(url); viewHolder.imageView.setImageResource(R.mipmap.ic_launcher);這兩句話十分重要,setTag是給ImageView綁定一個Tag,便於後面獲取;setImageResource則是在item剛剛加載的時候給ImageView設置一個默認圖片,這次是個android機器人,可自行設置。
哇咔咔,試試運行,大功告成有木有。但是出現了一個不好的用戶體驗,每次移出屏幕的圖片再次顯示必須得二次加載,這是極爲不好的。因此我們又接觸到了圖片緩存技術——lruCache。這裏就略微介紹一下,通過lruCache,分配一個合理的緩存空間,然後每加載一張就往裏面塞一張,超過設置的大小後釋放掉最不常用的緩存。有了這個算法,我們就可以輕易解決上述問題。
完善ImageAdapter:
package com.cjt_pc.listviewtest;
/**
* Created by cjt-pc on 2015/10/13.
* Email:879309896@qq.com
*/
import android.content.Context;
import android.graphics.Bitmap;
import android.os.AsyncTask;
import android.support.v4.util.LruCache;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.ArrayAdapter;
import android.widget.ImageView;
import android.widget.ListView;
import java.io.IOException;
import java.io.InputStream;
import java.net.URL;
public class ImageAdapter extends ArrayAdapter<String> {
private int resource;
private ListView mListView;
// 圖片緩存,以鍵值的方式存儲
private LruCache<String, Bitmap> mMemoryCache;
public ImageAdapter(Context context, int resource, String[] objects) {
super(context, resource, objects);
this.resource = resource;
// 獲取到可用內存的最大值,使用內存超出這個值會引起OutOfMemory異常。
// LruCache通過構造函數傳入緩存值,以KB爲單位。
int maxMemory = (int) (Runtime.getRuntime().maxMemory() / 1024);
// 使用最大可用內存值的1/8作爲緩存的大小。
int cacheSize = maxMemory / 8;
mMemoryCache = new LruCache<String, Bitmap>(cacheSize) {
@Override
protected int sizeOf(String key, Bitmap bitmap) {
// 重寫此方法來衡量每張圖片的大小,默認返回圖片數量。
return bitmap.getByteCount() / 1024;
}
};
}
// 添加bitmap到緩存中
public void addBitmapToMemoryCache(String key, Bitmap bitmap) {
if (getBitmapFromMemCache(key) == null) {
mMemoryCache.put(key, bitmap);
}
}
// 從緩存中獲取bitmap
public Bitmap getBitmapFromMemCache(String key) {
return mMemoryCache.get(key);
}
@Override
public View getView(int position, View convertView, ViewGroup parent) {
mListView = (ListView) parent;
String url = getItem(position);
ViewHolder viewHolder;
if (convertView == null) {
convertView = LayoutInflater.from(getContext()).inflate(resource, null);
viewHolder = new ViewHolder();
viewHolder.imageView = (ImageView) convertView.findViewById(R.id.image);
convertView.setTag(viewHolder);
} else {
viewHolder = (ViewHolder) convertView.getTag();
}
viewHolder.imageView.setTag(url);
viewHolder.imageView.setImageResource(R.mipmap.ic_launcher);
loadBitmap(url, viewHolder.imageView);
return convertView;
}
// 通過圖片緩存技術,快速重新加載和處理圖片
public void loadBitmap(String imageKey, ImageView imageView) {
Bitmap bitmap = getBitmapFromMemCache(imageKey);
if (bitmap != null) {
imageView.setImageBitmap(bitmap);
} else {
imageView.setImageResource(R.mipmap.ic_launcher);
new BitmapTask().execute(imageKey);
}
}
class ViewHolder {
ImageView imageView;
}
/**
* 異步下載圖片的任務。
*
* @author cjt-pc
*/
class BitmapTask extends AsyncTask<String, Void, Bitmap> {
private String url;
@Override
protected Bitmap doInBackground(String... strings) {
url = strings[0];
InputStream is = null;
Bitmap bitmap = null;
try {
is = new URL(url).openStream();
// 注意這裏沒有直接用decodeStream,是因爲由於本身的原因這樣極其不穩定
byte[] bytes = PictureCompressUtil.readStream(is);
// bitmap = BitmapFactory.decodeByteArray(bytes, 0, bytes.length);
bitmap = PictureCompressUtil.decodeSampledBitmapFromByteArray(bytes, 1440, 300);
} catch (Exception e) {
e.printStackTrace();
return null;
} finally {
try {
if (is != null) {
is.close();
}
} catch (IOException e) {
e.printStackTrace();
}
}
addBitmapToMemoryCache(url, bitmap);
return bitmap;
}
@Override
protected void onPostExecute(Bitmap bitmap) {
ImageView imageView = (ImageView) mListView.findViewWithTag(url);
if (imageView != null) {
imageView.setImageBitmap(bitmap);
}
}
}
}
這樣在滑動瀏覽的時候,移出屏幕的圖片會加載到緩存中,若該圖片在下一次加載的時候沒有被釋放掉,則直接讀取設置到ImageView上,既加強了用戶體驗,也使程序更爲的流暢。
嘖嘖,到了這裏,是不是就真的會用ListView了呢,我可以放心的告訴你,快了T T。
對於這個程序的選材,我可是花了一番功夫,圖片要大,又要多,還要好看。。。然而在快速滾動的時候仍顯不足,只要是觸發了getView並且緩存中沒有該圖片的會開啓線程異步加載,這樣的話快速滾動的話,會觸發很多的異步加載線程,但是滾動過去的圖片在加載完沒有多大的意義,當屏的圖片並沒有顯示,每個線程會分些網速,顯示圖片會越來越慢。所以我們要讓它在滑動的時候不進行任何操作,在停止滾動的時候加載當前屏幕內的圖片。
最終的ImageAdapter:
package com.cjt_pc.listviewtest;
/**
* Created by cjt-pc on 2015/10/13.
* Email:879309896@qq.com
*/
import android.content.Context;
import android.graphics.Bitmap;
import android.os.AsyncTask;
import android.support.v4.util.LruCache;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.AbsListView;
import android.widget.ArrayAdapter;
import android.widget.ImageView;
import android.widget.ListView;
import java.io.IOException;
import java.io.InputStream;
import java.net.URL;
import java.util.ArrayList;
import java.util.List;
public class ImageAdapter extends ArrayAdapter<String> implements AbsListView.OnScrollListener {
// 記錄異步加載任務的集合
List<BitmapTask> taskList = new ArrayList<>();
private int resource;
private ListView mListView;
// 圖片緩存,以鍵值的方式存儲
private LruCache<String, Bitmap> mMemoryCache;
private int mFirstVisibleItem;
private int mVisibleItemCount;
private boolean isFirstEnter = true;
public ImageAdapter(Context context, int resource, String[] objects) {
super(context, resource, objects);
this.resource = resource;
// 獲取到可用內存的最大值,使用內存超出這個值會引起OutOfMemory異常。
// LruCache通過構造函數傳入緩存值,以KB爲單位。
int maxMemory = (int) (Runtime.getRuntime().maxMemory() / 1024);
// 使用最大可用內存值的1/8作爲緩存的大小。
int cacheSize = maxMemory / 8;
mMemoryCache = new LruCache<String, Bitmap>(cacheSize) {
@Override
protected int sizeOf(String key, Bitmap bitmap) {
// 重寫此方法來衡量每張圖片的大小,默認返回圖片數量。
return bitmap.getByteCount() / 1024;
}
};
}
// 添加bitmap到緩存中
public void addBitmapToMemoryCache(String key, Bitmap bitmap) {
if (getBitmapFromMemCache(key) == null) {
mMemoryCache.put(key, bitmap);
}
}
// 從緩存中獲取bitmap
public Bitmap getBitmapFromMemCache(String key) {
return mMemoryCache.get(key);
}
@Override
public View getView(int position, View convertView, ViewGroup parent) {
mListView = (ListView) parent;
String url = getItem(position);
ViewHolder viewHolder;
if (convertView == null) {
convertView = LayoutInflater.from(getContext()).inflate(resource, null);
viewHolder = new ViewHolder();
viewHolder.imageView = (ImageView) convertView.findViewById(R.id.image);
convertView.setTag(viewHolder);
} else {
viewHolder = (ViewHolder) convertView.getTag();
}
viewHolder.imageView.setTag(url);
viewHolder.imageView.setImageResource(R.mipmap.ic_launcher);
loadBitmap(url, viewHolder.imageView);
return convertView;
}
// 通過圖片緩存技術,快速重新加載和處理圖片
public void loadBitmap(String imageKey, ImageView imageView) {
Bitmap bitmap = getBitmapFromMemCache(imageKey);
if (bitmap != null) {
imageView.setImageBitmap(bitmap);
}
}
@Override
public void onScrollStateChanged(AbsListView absListView, int i) {
// 僅當ListView靜止時纔去下載圖片,GridView滑動時取消所有正在下載的任務
if (i == SCROLL_STATE_IDLE) {
loadBitmaps(mFirstVisibleItem, mVisibleItemCount);
} else {
cancelAllTasks();
}
}
@Override
public void onScroll(AbsListView view, int firstVisibleItem, int visibleItemCount,
int totalItemCount) {
mFirstVisibleItem = firstVisibleItem;
mVisibleItemCount = visibleItemCount;
// 下載的任務應該由onScrollStateChanged裏調用,但首次進入程序時onScrollStateChanged並不會調用,
// 因此在這裏爲首次進入程序開啓下載任務。
if (isFirstEnter && visibleItemCount > 0) {
loadBitmaps(firstVisibleItem, visibleItemCount);
isFirstEnter = false;
}
}
/**
* 加載Bitmap對象。此方法會在LruCache中檢查所有屏幕中可見的ImageView的Bitmap對象,
* 如果發現任何一個ImageView的Bitmap對象不在緩存中,就會開啓異步線程去下載圖片。
*
* @param firstVisibleItem 第一個可見的ImageView的下標
* @param visibleItemCount 屏幕中總共可見的元素數
*/
private void loadBitmaps(int firstVisibleItem, int visibleItemCount) {
try {
for (int i = firstVisibleItem; i < firstVisibleItem + visibleItemCount; i++) {
String imageUrl = Images.imageUrls[i];
Bitmap bitmap = getBitmapFromMemCache(imageUrl);
if (bitmap == null) {
BitmapTask task = new BitmapTask();
taskList.add(task);
task.execute(imageUrl);
} else {
ImageView imageView = (ImageView) mListView.findViewWithTag(imageUrl);
if (imageView != null) {
imageView.setImageBitmap(bitmap);
}
}
}
} catch (Exception e) {
e.printStackTrace();
}
}
public void cancelAllTasks() {
if (!taskList.isEmpty()) {
// 遍歷任務集合,取消正在進行的異步任務,然後清空任務列表
for (BitmapTask task : taskList) {
task.cancel(true);
}
taskList.clear();
}
}
class ViewHolder {
ImageView imageView;
}
/**
* 異步下載圖片的任務。
*
* @author cjt-pc
*/
class BitmapTask extends AsyncTask<String, Void, Bitmap> {
private String url;
@Override
protected Bitmap doInBackground(String... strings) {
url = strings[0];
InputStream is = null;
Bitmap bitmap = null;
try {
is = new URL(url).openStream();
// 注意這裏沒有直接用decodeStream,是因爲由於本身的原因這樣極其不穩定
byte[] bytes = PictureCompressUtil.readStream(is);
// bitmap = BitmapFactory.decodeByteArray(bytes, 0, bytes.length);
bitmap = PictureCompressUtil.decodeSampledBitmapFromByteArray(bytes, 1440, 300);
} catch (Exception e) {
e.printStackTrace();
} finally {
try {
if (is != null) {
is.close();
}
} catch (IOException e) {
e.printStackTrace();
}
}
if (bitmap != null) {
addBitmapToMemoryCache(url, bitmap);
}
return bitmap;
}
@Override
protected void onPostExecute(Bitmap bitmap) {
ImageView imageView = (ImageView) mListView.findViewWithTag(url);
if (imageView != null && bitmap != null) {
imageView.setImageBitmap(bitmap);
}
// 要記得在異步加載過程完成後將本次任務移出
taskList.remove(this);
}
}
}
主要的就是讓ImageAdapter實現了OnScorllListener的接口,然後要記住在Activity中加上監聽:
package com.cjt_pc.listviewtest;
import android.app.Activity;
import android.os.Bundle;
import android.widget.AbsListView;
import android.widget.ListView;
public class MainActivity extends Activity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
ListView listView = (ListView) findViewById(R.id.list_view);
ImageAdapter adapter = new ImageAdapter(this, R.layout.image_item, Images.imageUrls);
listView.setAdapter(adapter);
listView.setOnScrollListener(adapter);
}
}
額,差不多了。總的來說,就四個方面
- 圖片壓縮
- 放置亂序
- 緩存技術
- 滑動監聽
具體的上面都有,好了,就是這些了,我們終於敢說我會用ListView了額,GridView原理都是一樣。今天就到這了,拜了個拜^-^。