作者punk86862001 (赵MAN)
看板AndroidDev
标题[分享] Android 非同步下载显示图片ImageLoader
时间Tue Feb 26 19:12:04 2013
原文网址
http://kenji-chao.blogspot.tw/2013/02/android-universal-image-loader.html
前言
在开发 Android 的过程中,我们常常需要在 ListView, GridView, Gallery 或是 ViewPager 中显示图片,然而大部分图片的来源都是来自网路或是 SD 卡,为了节省时间,我们总不能等到所有图片都下载完成以後在显示给使用者看,因此必须先显示 Main Thread 的 View,并开启 Multi Thread 让图片在背景下载,等到下载完成後再显示到 Main Thread 的 View 上,如下图所示。
原始 Project 的连结请按 这里
特点
开启多个 Thread 同步下载图片
可以自订 Image Loader 的设定 (thread pool size, HTTP options, memory and disc cache, display image options, and others)
可以将图片图片的快取存在 Memory 或是 SD 卡
支援 Android 1.5+
范例实作
用手机扫描以下载范例 APK档
目标
用 ListView 去实作此 Image Loader
第一次显示图片时有 Fade in 的效果
可自行设定 default, 空url, 错误等特殊情况的图片
具有清除 Memory 及 SD 卡快取的功能
范例程式画面
1. 加入 Library
先下载 JAR档
将此档案加入 Android Project 的 libs 资料夹
2. 设定 manifest 及 xml 档
Androidmanifest.xml:加入网路以及以及写入SD卡的权限
<uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
main.xml:主画面使用的layout
<RelativeLayout xmlns:android="
http://schemas.android.com/apk/res/android"
xmlns:tools="
http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent" >
<ListView
android:id="@+id/listView1"
android:layout_width="fill_parent"
android:layout_height="fill_parent"
android:layout_centerHorizontal="true"
android:layout_centerVertical="true" >
</ListView>
</RelativeLayout>
list_item.xml:ListView 的 item 所需的 layout
<?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="wrap_content" >
<ImageView
android:id="@+id/image"
android:layout_width="72dp"
android:layout_height="72dp"
android:layout_margin="6dp"
android:adjustViewBounds="true"
android:scaleType="centerCrop" />
<TextView
android:id="@+id/text"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:layout_gravity="left|center_vertical"
android:layout_marginLeft="20dip"
android:textSize="22sp" />
</LinearLayout>
3. 初始化设定
首先先 initialize 所需的变数 options 跟 imageLoader
DisplayImageOptions options;
ImageLoader imageLoader = ImageLoader.getInstance();
并在 onCreate 里面加上 option 跟 config 的详细设定
// ImageLoader 初始设定
options = new DisplayImageOptions.Builder()
.showStubImage(R.drawable.ic_stub)
.showImageForEmptyUri(R.drawable.ic_empty)
.showImageOnFail(R.drawable.ic_error)
.cacheInMemory()
.cacheOnDisc()
.build();
ImageLoaderConfiguration config = new ImageLoaderConfiguration.Builder(getApplicationContext())
.defaultDisplayImageOptions(options)
.build();
imageLoader.init(config);
初始化所需要呈现的图片来源
public static final String[] IMAGES = new String[] {
// 大图片们
"
https://lh6.googleusercontent.com/-jZgveEqb6pg/T3R4kXScycI/AAAAAAAAAE0/xQ7CvpfXDzc/s1024/sample_image_01.jpg",
"
https://lh4.googleusercontent.com/-K2FMuOozxU0/T3R4lRAiBTI/AAAAAAAAAE8/a3Eh9JvnnzI/s1024/sample_image_02.jpg",
"
https://lh5.googleusercontent.com/-SCS5C646rxM/T3R4l7QB6xI/AAAAAAAAAFE/xLcuVv3CUyA/s1024/sample_image_03.jpg",
"
https://lh6.googleusercontent.com/-f0NJR6-_Thg/T3R4mNex2wI/AAAAAAAAAFI/45oug4VE8MI/s1024/sample_image_04.jpg",
"
https://lh3.googleusercontent.com/-n-xcJmiI0pg/T3R4mkSchHI/AAAAAAAAAFU/EoiNNb7kk3A/s1024/sample_image_05.jpg",
"
https://lh3.googleusercontent.com/-X43vAudm7f4/T3R4nGSChJI/AAAAAAAAAFk/3bna6D-2EE8/s1024/sample_image_06.jpg",
"
https://lh5.googleusercontent.com/-MpZneqIyjXU/T3R4nuGO1aI/AAAAAAAAAFg/r09OPjLx1ZY/s1024/sample_image_07.jpg",
"
https://lh6.googleusercontent.com/-ql3YNfdClJo/T3XvW9apmFI/AAAAAAAAAL4/_6HFDzbahc4/s1024/sample_image_08.jpg",
"
https://lh5.googleusercontent.com/-Pxa7eqF4cyc/T3R4oasvPEI/AAAAAAAAAF0/-uYDH92h8LA/s1024/sample_image_09.jpg",
"
https://lh4.googleusercontent.com/-Li-rjhFEuaI/T3R4o-VUl4I/AAAAAAAAAF8/5E5XdMnP1oE/s1024/sample_image_10.jpg",
"
https://lh5.googleusercontent.com/-_HU4fImgFhA/T3R4pPVIwWI/AAAAAAAAAGA/0RfK_Vkgth4/s1024/sample_image_11.jpg",
"
https://lh6.googleusercontent.com/-0gnNrVjwa0Y/T3R4peGYJwI/AAAAAAAAAGU/uX_9wvRPM9I/s1024/sample_image_12.jpg",
"
https://lh3.googleusercontent.com/-HBxuzALS_Zs/T3R4qERykaI/AAAAAAAAAGQ/_qQ16FaZ1q0/s1024/sample_image_13.jpg",
"
https://lh4.googleusercontent.com/-cKojDrARNjQ/T3R4qfWSGPI/AAAAAAAAAGY/MR5dnbNaPyY/s1024/sample_image_14.jpg",
"
https://lh3.googleusercontent.com/-WujkdYfcyZ8/T3R4qrIMGUI/AAAAAAAAAGk/277LIdgvnjg/s1024/sample_image_15.jpg",
"
https://lh6.googleusercontent.com/-FMHR7Vy3PgI/T3R4rOXlEKI/AAAAAAAAAGs/VeXrDNDBkaw/s1024/sample_image_16.jpg",
"
https://lh4.googleusercontent.com/-mrR0AJyNTH0/T3R4rZs6CuI/AAAAAAAAAG0/UE1wQqCOqLA/s1024/sample_image_17.jpg",
"
https://lh6.googleusercontent.com/-z77w0eh3cow/T3R4rnLn05I/AAAAAAAAAG4/BaerfWoNucU/s1024/sample_image_18.jpg",
"
https://lh5.googleusercontent.com/-aWVwh1OU5Bk/T3R4sAWw0yI/AAAAAAAAAHE/4_KAvJttFwA/s1024/sample_image_19.jpg",
"
https://lh6.googleusercontent.com/-q-js52DMnWQ/T3R4tZhY2sI/AAAAAAAAAHM/A8kjp2Ivdqg/s1024/sample_image_20.jpg",
"
https://lh5.googleusercontent.com/-_jIzvvzXKn4/T3R4t7xpdVI/AAAAAAAAAHU/7QC6eZ10jgs/s1024/sample_image_21.jpg",
"
https://lh3.googleusercontent.com/-lnGi4IMLpwU/T3R4uCMa7vI/AAAAAAAAAHc/1zgzzz6qTpk/s1024/sample_image_22.jpg",
"
https://lh5.googleusercontent.com/-fFCzKjFPsPc/T3R4u0SZPFI/AAAAAAAAAHk/sbgjzrktOK0/s1024/sample_image_23.jpg",
"
https://lh4.googleusercontent.com/-8TqoW5gBE_Y/T3R4vBS3NPI/AAAAAAAAAHs/EZYvpNsaNXk/s1024/sample_image_24.jpg",
"
https://lh6.googleusercontent.com/-gc4eQ3ySdzs/T3R4vafoA7I/AAAAAAAAAH4/yKii5P6tqDE/s1024/sample_image_25.jpg",
"
https://lh5.googleusercontent.com/--NYOPCylU7Q/T3R4vjAiWkI/AAAAAAAAAH8/IPNx5q3ptRA/s1024/sample_image_26.jpg",
"
https://lh6.googleusercontent.com/-9IJM8so4vCI/T3R4vwJO2yI/AAAAAAAAAIE/ljlr-cwuqZM/s1024/sample_image_27.jpg",
"
https://lh4.googleusercontent.com/-KW6QwOHfhBs/T3R4w0RsQiI/AAAAAAAAAIM/uEFLVgHPFCk/s1024/sample_image_28.jpg",
"
https://lh4.googleusercontent.com/-z2557Ec1ctY/T3R4x3QA2hI/AAAAAAAAAIk/9-GzPL1lTWE/s1024/sample_image_29.jpg",
"
https://lh5.googleusercontent.com/-LaKXAn4Kr1c/T3R4yc5b4lI/AAAAAAAAAIY/fMgcOVQfmD0/s1024/sample_image_30.jpg",
"
https://lh4.googleusercontent.com/-F9LRToJoQdo/T3R4yrLtyQI/AAAAAAAAAIg/ri9uUCWuRmo/s1024/sample_image_31.jpg",
"
https://lh4.googleusercontent.com/-6X-xBwP-QpI/T3R4zGVboII/AAAAAAAAAIs/zYH4PjjngY0/s1024/sample_image_32.jpg",
"
https://lh5.googleusercontent.com/-VdLRjbW4LAs/T3R4zXu3gUI/AAAAAAAAAIw/9aFp9t7mCPg/s1024/sample_image_33.jpg",
"
https://lh6.googleusercontent.com/-gL6R17_fDJU/T3R4zpIXGjI/AAAAAAAAAI8/Q2Vjx-L9X20/s1024/sample_image_34.jpg",
"
https://lh3.googleusercontent.com/-1fGH4YJXEzo/T3R40Y1B7KI/AAAAAAAAAJE/MnTsa77g-nk/s1024/sample_image_35.jpg",
"
https://lh4.googleusercontent.com/-Ql0jHSrea-A/T3R403mUfFI/AAAAAAAAAJM/qzI4SkcH9tY/s1024/sample_image_36.jpg",
"
https://lh5.googleusercontent.com/-BL5FIBR_tzI/T3R41DA0AKI/AAAAAAAAAJk/GZfeeb-SLM0/s1024/sample_image_37.jpg",
"
https://lh4.googleusercontent.com/-wF2Vc9YDutw/T3R41fR2BCI/AAAAAAAAAJc/JdU1sHdMRAk/s1024/sample_image_38.jpg",
"
https://lh6.googleusercontent.com/-ZWHiPehwjTI/T3R41zuaKCI/AAAAAAAAAJg/hR3QJ1v3REg/s1024/sample_image_39.jpg",
// 小图片们
"
http://tabletpcssource.com/wp-content/uploads/2011/05/android-logo.png",
"
http://simpozia.com/pages/images/stories/windows-icon.png",
"
https://si0.twimg.com/profile_images/1135218951/gmail_profile_icon3_normal.png",
"
http://www.krify.net/wp-content/uploads/2011/09/Macromedia_Flash_dock_icon.png",
"
http://radiotray.sourceforge.net/radio.png",
"
http://www.bandwidthblog.com/wp-content/uploads/2011/11/twitter-logo.png",
"
http://weloveicons.s3.amazonaws.com/icons/100907_itunes1.png",
"
http://weloveicons.s3.amazonaws.com/icons/100929_applications.png",
"
http://www.idyllicmusic.com/index_files/get_apple-iphone.png",
"
http://www.frenchrevolutionfood.com/wp-content/uploads/2009/04/Twitter-Bird.png",
"
http://3.bp.blogspot.com/-ka5MiRGJ_S4/TdD9OoF6bmI/AAAAAAAAE8k/7ydKtptUtSg/s1600/Google_Sky%2BMaps_Android.png",
"
http://www.desiredsoft.com/images/icon_webhosting.png",
"
http://goodereader.com/apps/wp-content/uploads/downloads/thumbnails/2012/01/hi-256-0-99dda8c730196ab93c67f0659d5b8489abdeb977.png",
"
http://1.bp.blogspot.com/-mlaJ4p_3rBU/TdD9OWxN8II/AAAAAAAAE8U/xyynWwr3_4Q/s1600/antivitus_free.png",
"
http://cdn3.iconfinder.com/data/icons/transformers/computer.png",
"
http://cdn.geekwire.com/wp-content/uploads/2011/04/firefox.png?7794fe",
"
https://ssl.gstatic.com/android/market/com.rovio.angrybirdsseasons/hi-256-9-347dae230614238a639d21508ae492302340b2ba",
"
http://androidblaze.com/wp-content/uploads/2011/12/tablet-pc-256x256.jpg",
"
http://www.theblaze.com/wp-content/uploads/2011/08/Apple.png",
"
http://1.bp.blogspot.com/-y-HQwQ4Kuu0/TdD9_iKIY7I/AAAAAAAAE88/3G4xiclDZD0/s1600/Twitter_Android.png",
"
http://3.bp.blogspot.com/-nAf4IMJGpc8/TdD9OGNUHHI/AAAAAAAAE8E/VM9yU_lIgZ4/s1600/Adobe%2BReader_Android.png",
"
http://cdn.geekwire.com/wp-content/uploads/2011/05/oovoo-android.png?7794fe",
"
http://icons.iconarchive.com/icons/kocco/ndroid/128/android-market-2-icon.png",
"
http://thecustomizewindows.com/wp-content/uploads/2011/11/Nicest-Android-Live-Wallpapers.png",
"
http://c.wrzuta.pl/wm16596/a32f1a47002ab3a949afeb4f",
"
http://macprovid.vo.llnwd.net/o43/hub/media/1090/6882/01_headline_Muse.jpg",
// 特殊情况们
"file:///sdcard/UniversalImageLoader.png", // Image from SD card
"assets://LivingThings.jpg", // Image from assets
"drawable://" + R.drawable.ic_launcher, // Image from drawables
"
http://upload.wikimedia.org/wikipedia/ru/b/b6/訄郕_郕郋__邾訄邾邽_赲郋迮赲訄郅.png", // Link with UTF-8
"
https://www.eff.org/sites/default/files/chrome150_0.jpg", // Image from HTTPS
"
http://bit.ly/soBiXr", // Redirect link
"", // Empty link
"
http://wrong.site.com/corruptedLink", // Wrong link
};
4. 设定图片第一次显示的 Fade in 动画
用一个List去记录已经显示过的图片,如果是第一次出现就显示Fade in动画,反之则否。
// 图片显示动画
private static class AnimateFirstDisplayListener extends SimpleImageLoadingListener {
static final List displayedImages = Collections.synchronizedList(new LinkedList());
@Override
public void onLoadingComplete(String imageUri, View view, Bitmap loadedImage) {
if (loadedImage != null) {
ImageView imageView = (ImageView) view;
boolean firstDisplay = !displayedImages.contains(imageUri);
if (firstDisplay) {
FadeInBitmapDisplayer.animate(imageView, 500);
} else {
imageView.setImageBitmap(loadedImage);
}
displayedImages.add(imageUri);
}
}
}
5. 设定 ListView
先定义自己的 List Adapter
// ListView Adapter
class ItemAdapter extends BaseAdapter {
private ImageLoadingListener animateFirstListener = new AnimateFirstDisplayListener();
private class ViewHolder {
public TextView text;
public ImageView image;
}
@Override
public int getCount() {
return IMAGES.length;
}
@Override
public Object getItem(int position) {
return position;
}
@Override
public long getItemId(int position) {
return position;
}
@Override
public View getView(final int position, View convertView, ViewGroup parent) {
View view = convertView;
final ViewHolder holder;
if (convertView == null) {
view = getLayoutInflater().inflate(R.layout.list_item, parent, false);
holder = new ViewHolder();
holder.text = (TextView) view.findViewById(R.id.text);
holder.image = (ImageView) view.findViewById(R.id.image);
view.setTag(holder);
} else {
holder = (ViewHolder) view.getTag();
}
holder.text.setText("这是第 "+ (position+1) +" 项");
imageLoader.displayImage(IMAGES[position], holder.image, options, animateFirstListener);
return view;
}
}
在onCreate里头加入ListView的设定
// ListView 设定
ListView listview = (ListView) findViewById(R.id.listView1);
listview.setAdapter(new ItemAdapter());
listview.setOnItemClickListener(new OnItemClickListener(){
@Override
public void onItemClick(AdapterView arg0, View arg1, int arg2,
long arg3) {
// TODO Auto-generated method stub
Toast.makeText(MainActivity.this, "这是第 " + (arg2+1) + " 项!", Toast.LENGTH_SHORT).show();
}
});
6. 设定 Menu
在 res/menu/ 底下建立main_menu.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="wrap_content" >
<ImageView
android:id="@+id/image"
android:layout_width="72dp"
android:layout_height="72dp"
android:layout_margin="6dp"
android:adjustViewBounds="true"
android:scaleType="centerCrop" />
<TextView
android:id="@+id/text"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:layout_gravity="left|center_vertical"
android:layout_marginLeft="20dip"
android:textSize="22sp" />
</LinearLayout>
加入menu所需要的两个功能
// 设定 menu
@Override
public boolean onCreateOptionsMenu(Menu menu) {
getMenuInflater().inflate(R.menu.main_menu, menu);
return true;
}
@Override
public boolean onOptionsItemSelected(MenuItem item) {
switch (item.getItemId()) {
case R.id.item_clear_memory_cache:
imageLoader.clearMemoryCache();
return true;
case R.id.item_clear_disc_cache:
imageLoader.clearDiscCache();
return true;
default:
return false;
}
}
7. 记忆体维护
最後在离开程式时停止imageLoader跟动画
@Override
public void onBackPressed() {
imageLoader.stop();
AnimateFirstDisplayListener.displayedImages.clear();
super.onBackPressed();
}
如此一来就大功告成了!!
按 这里 下载原始码
结语
这是我第一次写教学,如果有什麽不清楚的地方
欢迎给我意见!大家一起加油吧YA!
--
※ 发信站: 批踢踢实业坊(ptt.cc)
◆ From: 223.142.114.124
1F:推 adbo1108:感谢分享 02/26 19:22
2F:推 colza:感谢大大分享!先推再来拜读 02/26 20:30
3F:→ diousk:记得有个LazyAdapter也是在做类似的事~ 02/28 15:57