精華區beta AndroidDev 關於我們 聯絡資訊
原文網址 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
adbo1108:感謝分享 02/26 19:22
colza:感謝大大分享!先推再來拜讀 02/26 20:30
diousk:記得有個LazyAdapter也是在做類似的事~ 02/28 15:57