昨天參加一個面試,面試官讓當場寫一個相似於新聞列表的頁面,文本數據和圖片都從網絡上獲取,想起我還沒寫過ListView異步加載圖片並實現圖文混排效果的文章,so,今天就來寫一下,介紹一下經驗。android
ListView加載文本數據都是很簡單的,即便是異步獲取文本數據。可是異步加載圖片就稍微有一點麻煩,既要得到一個比較好的用戶體驗,還要防止出現圖片錯位等各類不良BUG,其實要考慮的東西仍是挺多的。好了,咱們先來看一下咱們今天要實現的一個效果圖:git
看起來彷佛並不難,確實,咱們今天的核心問題只有一個,就是怎麼異步加載圖片,而且沒有違和感。github
好了,廢話很少說,先來看主佈局文件:web
1 <RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android" 2 xmlns:tools="http://schemas.android.com/tools" 3 android:layout_width="match_parent" 4 android:layout_height="match_parent" 5 tools:context="com.example.listview.MainActivity" > 6 7 <ListView 8 android:id="@+id/lv" 9 android:layout_width="match_parent" 10 android:layout_height="match_parent" > 11 </ListView> 12 13 </RelativeLayout>
主佈局中就一個listview,看看listview的item佈局文件:面試
1 <?xml version="1.0" encoding="utf-8"?> 2 <RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android" 3 android:layout_width="match_parent" 4 android:layout_height="100dp" > 5 6 <ImageView 7 android:id="@+id/iv" 8 android:layout_width="80dp" 9 android:layout_height="90dp" 10 android:layout_centerVertical="true" 11 android:padding="5dp" 12 android:src="@drawable/ic_launcher" /> 13 14 <TextView 15 android:id="@+id/title" 16 android:layout_width="match_parent" 17 android:layout_height="wrap_content" 18 android:layout_marginTop="20dp" 19 android:layout_toRightOf="@id/iv" 20 android:gravity="center_vertical" 21 android:text="人社部:養老轉移已有初稿" 22 android:textSize="14sp" 23 android:textStyle="bold" /> 24 25 <TextView 26 android:id="@+id/summary" 27 android:layout_width="match_parent" 28 android:layout_height="wrap_content" 29 android:layout_below="@id/title" 30 android:layout_marginTop="8dp" 31 android:layout_toRightOf="@id/iv" 32 android:text="人社部:養老轉移已有初稿" 33 android:textSize="12sp" /> 34 35 </RelativeLayout>
這個佈局和咱們上圖描述的同樣,左邊一個ImageView,右邊是兩個TextView,這些都不難,咱們看看MainActivity:json
1 public class MainActivity extends Activity { 2 3 private ListView lv; 4 private List<News> list; 5 private String HTTPURL = "http://litchiapi.jstv.com/api/GetFeeds?column=3&PageSize=20&pageIndex=1&val=100511D3BE5301280E0992C73A9DEC41"; 6 private Handler mHandler = new Handler(){ 7 8 @Override 9 public void handleMessage(Message msg) { 10 super.handleMessage(msg); 11 switch (msg.what) { 12 case 0: 13 MyAdaper adapter = new MyAdaper(list); 14 lv.setAdapter(adapter); 15 break; 16 17 default: 18 break; 19 } 20 } 21 22 }; 23 24 @Override 25 protected void onCreate(Bundle savedInstanceState) { 26 super.onCreate(savedInstanceState); 27 setContentView(R.layout.activity_main); 28 lv = (ListView) this.findViewById(R.id.lv); 29 initData(); 30 } 31 32 private void initData() { 33 list = new ArrayList<News>(); 34 35 OkHttpClient client = new OkHttpClient(); 36 Request request = new Request.Builder().url(HTTPURL).build(); 37 Call call = client.newCall(request); 38 call.enqueue(new Callback() { 39 40 @Override 41 public void onResponse(Response response) throws IOException { 42 try { 43 JSONObject jo1 = new JSONObject(response.body().string()); 44 JSONObject jo2 = jo1.getJSONObject("paramz"); 45 JSONArray ja = jo2.getJSONArray("feeds"); 46 News news = null; 47 for (int i = 0; i < ja.length(); i++) { 48 JSONObject data = ja.getJSONObject(i).getJSONObject( 49 "data"); 50 String imageUrl = "http://litchiapi.jstv.com" 51 + data.getString("cover"); 52 String title = data.getString("subject"); 53 String summary = data.getString("summary"); 54 news = new News(imageUrl, title, summary); 55 list.add(news); 56 } 57 } catch (JSONException e) { 58 e.printStackTrace(); 59 } 60 mHandler.obtainMessage(0).sendToTarget(); 61 } 62 63 @Override 64 public void onFailure(Request arg0, IOException arg1) { 65 66 } 67 }); 68 } 69 }
在onCreate方法中,咱們先拿到一個ListView的實例,而後就是初始化數據,這裏初始化數據咱們使用了OKHttp,關於OKHttp的使用能夠查看我以前的文章OKHttp的簡單使用,咱們拿到一串json數據,至於json裏邊的結構是怎麼樣的,我就很少說了,你們能夠直接在瀏覽器中打開上面的地址,這樣就能看到json數據了,咱們把咱們須要的數據封裝成一個JavaBean,其中ImageView咱們先存儲一個url地址,而後在Adapter中經過這個url地址異步加載圖片。json解析就很少說了,咱們瞅一眼這個Bean:api
1 public class News { 2 3 private String imageUrl; 4 private String title; 5 private String summary; 6 7 public String getImageUrl() { 8 return imageUrl; 9 } 10 11 public void setImageUrl(String imageUrl) { 12 this.imageUrl = imageUrl; 13 } 14 15 public String getTitle() { 16 return title; 17 } 18 19 public void setTitle(String title) { 20 this.title = title; 21 } 22 23 public String getSummary() { 24 return summary; 25 } 26 27 public void setSummary(String summary) { 28 this.summary = summary; 29 } 30 31 public News(String imageUrl, String title, String summary) { 32 this.imageUrl = imageUrl; 33 this.title = title; 34 this.summary = summary; 35 } 36 37 public News() { 38 } 39 }
好了,到這裏,全部的東西都仍是基本用法,下面咱們先不急着看Adapter,先來看看Google給咱們提供的一個緩存機制,在android-support-v4.jar包中,Google提供了這樣一個類LruCache,這個LruCache的使用和Java中的Map用法差很少,甚至你就能夠把它看成Map來使用,不一樣的是LruCache中的Value能夠是一張圖片。若是咱們緩存的圖片太多,超出了咱們設置的緩存大小,那麼系統會自動移除咱們在最近使用比較少的圖片。好了,咱們來看看LruCache的定義:瀏覽器
1 private LruCache<String, BitmapDrawable> mImageCache;
每一個圖片的緩存的key咱們就使用該圖片的url(這個是惟一的),value就是一張咱們要緩存的圖片,在實例化LruCache的時候,咱們須要傳入一個參數,代表咱們可使用的最大緩存,這個緩存參數咱們傳入可用緩存的1/8,同時咱們須要重寫sizeOf方法,查看源碼咱們能夠知道,若是不重寫sizeOf方法,它默認返回的是圖片的數量,可是咱們其實是須要計算圖片大小來判斷當前已經使用的緩存是否已經超出界限,因此咱們這裏重寫sizeOf方法,返回每張圖片的大小。代碼以下:緩存
1 int maxCache = (int) Runtime.getRuntime().maxMemory(); 2 int cacheSize = maxCache / 8; 3 mImageCache = new LruCache<String, BitmapDrawable>(cacheSize) { 4 @Override 5 protected int sizeOf(String key, BitmapDrawable value) { 6 return value.getBitmap().getByteCount(); 7 } 8 };
從LruCache中讀取一張圖片的方式和從Map中取值是同樣的:網絡
1 mImageCache.get(key)
向LruCache中存儲一張圖片:
1 mImageCache.put(key, bitmapDrawable);
關於LruCache的基本用法就說這些,這已經夠咱們後面使用了,如今我就大概說說咱們的一個思路,當咱們要給ImageView設置圖片的時候,就先在本地緩存中查看是否有該圖片,有的話,直接從本地讀取,沒有的話就從網絡請求,同時,在從網絡請求圖片的時候,爲了防止發生圖片錯位的狀況,咱們要給每個item的每個ImageView設置一個tag,這個tag就使用該ImageView要加載的圖片的url(這樣就能夠確保每個ImageView惟一),在給ImageView設置圖片的時候咱們就能夠經過這個tag找到咱們須要的ImageView,這樣能夠有效避免圖片錯位的問題。好了,看代碼:
1 public class MyAdaper extends BaseAdapter { 2 3 private List<News> list; 4 private ListView listview; 5 private LruCache<String, BitmapDrawable> mImageCache; 6 7 public MyAdaper(List<News> list) { 8 super(); 9 this.list = list; 10 11 int maxCache = (int) Runtime.getRuntime().maxMemory(); 12 int cacheSize = maxCache / 8; 13 mImageCache = new LruCache<String, BitmapDrawable>(cacheSize) { 14 @Override 15 protected int sizeOf(String key, BitmapDrawable value) { 16 return value.getBitmap().getByteCount(); 17 } 18 }; 19 20 } 21 22 @Override 23 public int getCount() { 24 return list.size(); 25 } 26 27 @Override 28 public Object getItem(int position) { 29 return list.get(position); 30 } 31 32 @Override 33 public long getItemId(int position) { 34 return position; 35 } 36 37 @Override 38 public View getView(int position, View convertView, ViewGroup parent) { 39 if (listview == null) { 40 listview = (ListView) parent; 41 } 42 ViewHolder holder = null; 43 if (convertView == null) { 44 convertView = LayoutInflater.from(parent.getContext()).inflate( 45 R.layout.listview_item, null); 46 holder = new ViewHolder(); 47 holder.iv = (ImageView) convertView.findViewById(R.id.iv); 48 holder.title = (TextView) convertView.findViewById(R.id.title); 49 holder.summary = (TextView) convertView.findViewById(R.id.summary); 50 convertView.setTag(holder); 51 } else { 52 holder = (ViewHolder) convertView.getTag(); 53 } 54 News news = list.get(position); 55 holder.title.setText(news.getTitle()); 56 holder.summary.setText(news.getSummary()); 57 holder.iv.setTag(news.getImageUrl()); 58 // 若是本地已有緩存,就從本地讀取,不然從網絡請求數據 59 if (mImageCache.get(news.getImageUrl()) != null) { 60 holder.iv.setImageDrawable(mImageCache.get(news.getImageUrl())); 61 } else { 62 ImageTask it = new ImageTask(); 63 it.execute(news.getImageUrl()); 64 } 65 return convertView; 66 } 67 68 class ViewHolder { 69 ImageView iv; 70 TextView title, summary; 71 } 72 73 class ImageTask extends AsyncTask<String, Void, BitmapDrawable> { 74 75 private String imageUrl; 76 77 @Override 78 protected BitmapDrawable doInBackground(String... params) { 79 imageUrl = params[0]; 80 Bitmap bitmap = downloadImage(); 81 BitmapDrawable db = new BitmapDrawable(listview.getResources(), 82 bitmap); 83 // 若是本地還沒緩存該圖片,就緩存 84 if (mImageCache.get(imageUrl) == null) { 85 mImageCache.put(imageUrl, db); 86 } 87 return db; 88 } 89 90 @Override 91 protected void onPostExecute(BitmapDrawable result) { 92 // 經過Tag找到咱們須要的ImageView,若是該ImageView所在的item已被移出頁面,就會直接返回null 93 ImageView iv = (ImageView) listview.findViewWithTag(imageUrl); 94 if (iv != null && result != null) { 95 iv.setImageDrawable(result); 96 } 97 } 98 99 /** 100 * 根據url從網絡上下載圖片 101 * 102 * @return 103 */ 104 private Bitmap downloadImage() { 105 HttpURLConnection con = null; 106 Bitmap bitmap = null; 107 try { 108 URL url = new URL(imageUrl); 109 con = (HttpURLConnection) url.openConnection(); 110 con.setConnectTimeout(5 * 1000); 111 con.setReadTimeout(10 * 1000); 112 bitmap = BitmapFactory.decodeStream(con.getInputStream()); 113 } catch (MalformedURLException e) { 114 e.printStackTrace(); 115 } catch (IOException e) { 116 e.printStackTrace(); 117 } finally { 118 if (con != null) { 119 con.disconnect(); 120 } 121 } 122 123 return bitmap; 124 } 125 126 } 127 128 }
好了,listview圖文混排就說到這裏,有問題歡迎留言討論。