版权声明:本文为博主原创文章,可任意转载,但转载需注明出处。 https://blog.csdn.net/u013370358/article/details/52441485
这篇文章是根据慕课网上的ListView的视频讲解做的练习:
1.视频链接:http://www.imooc.com/video/7871
2.json数据获取地址:http://www.imooc.com/api/teacher?type=4&num=30
数据格式截图:
app效果展示:
一共有四个类,两个布局文件:
1.activity的布局文件:activity_main.xml
<?xml version="1.0" encoding="utf-8"?>
<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"
android:paddingBottom="@dimen/activity_vertical_margin"
android:paddingLeft="@dimen/activity_horizontal_margin"
android:paddingRight="@dimen/activity_horizontal_margin"
android:paddingTop="@dimen/activity_vertical_margin"
tools:context="com.example.admin.mylistviewdemo.MainActivity">
<ListView
android:id="@+id/lv_main"
android:layout_width="match_parent"
android:layout_height="wrap_content" />
</RelativeLayout>
2.具体列表项的布局:item_layout.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="wrap_content"
android:orientation="horizontal">
<ImageView
android:id="@+id/iv_icon"
android:layout_width="64dp"
android:layout_height="64dp"
android:src="@mipmap/ic_launcher" />
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_gravity="center"
android:orientation="vertical"
android:paddingLeft="4dp">
<TextView
android:id="@+id/tv_title"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:maxLines="1"
android:text="title"
android:textSize="18sp" />
<TextView
android:id="@+id/tv_content"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:maxLines="3"
android:text="content"
android:textSize="13sp" />
</LinearLayout>
</LinearLayout>
3.MainActivity.java文件
package com.example.admin.mylistviewdemo;
import android.os.AsyncTask;
import android.os.Bundle;
import android.support.v7.app.AppCompatActivity;
import android.util.Log;
import android.widget.ListView;
import org.json.JSONArray;
import org.json.JSONException;
import org.json.JSONObject;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.UnsupportedEncodingException;
import java.net.URL;
import java.util.ArrayList;
import java.util.List;
public class MainActivity extends AppCompatActivity {
public ListView mListView;
public List<NewsBean> mNewsBeanList;
public static String JSONPATH = "http://www.imooc.com/api/teacher?type=4&num=30";
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
mListView = (ListView) findViewById(R.id.lv_main);
mNewsBeanList = new ArrayList<>();
new NewsAsyncTask().execute(JSONPATH);
}
//用于根据指定的Url异步地获取json数据
class NewsAsyncTask extends AsyncTask<String, Void, List<NewsBean>> {
@Override
protected void onPostExecute(List<NewsBean> newsBeen) {
super.onPostExecute(newsBeen);
NewsAdapter mNewsAdapter = new NewsAdapter(MainActivity.this,newsBeen,mListView);
mListView.setAdapter(mNewsAdapter);
}
@Override
protected List<NewsBean> doInBackground(String... params) {
//这里传进来的参数就是我们需要解析的json数据的地址,因为就只有一个参数,
// 因此取参数数组的第一个元素params[0]
return getJsonData(params[0]);
}
}
//从指定的流中读取json数据,将得到的json数据转化为String类型的字符串
public String readStream(InputStream is) {
String result = "", line;
BufferedReader bf = null;
try {
//将输入流读入字符缓冲数组中
bf = new BufferedReader(new InputStreamReader(is, "utf-8"));
while ((line = bf.readLine()) != null) {
result += line;
}
return result;
} catch (UnsupportedEncodingException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}
return null;
}
//通过指定的Url来解析json数据,封装进NewsBean,并且装入mList
public List<NewsBean> getJsonData(String url) {
//用于将数据封装到该NewsBean中
NewsBean mNewsBean;
try {
String mJsonString = readStream(new URL(JSONPATH).openStream());
JSONObject mJSONObject = new JSONObject(mJsonString);
JSONArray mJSONArray = mJSONObject.getJSONArray("data");
for (int i = 0; i < mJSONArray.length(); i++) {
//获得json数组中的子json对象
mJSONObject = mJSONArray.getJSONObject(i);
//将数据封装到该NewsBean中
mNewsBean = new NewsBean();
mNewsBean.newsTitle = mJSONObject.getString("name");
mNewsBean.newsContent = mJSONObject.getString("description");
mNewsBean.newsIconUrl = mJSONObject.getString("picSmall");
//添加该NewsBean到List中
mNewsBeanList.add(mNewsBean);
}
return mNewsBeanList;
} catch (IOException e) {
Log.d("qc", "网络连接故障");
System.out.println("网络连接故障");
e.printStackTrace();
} catch (JSONException e) {
Log.d("qc", "json数据异常");
System.out.println("json数据异常");
e.printStackTrace();
}
return null;
}
}
在该类中,我们主要通过在AsyncTackzhong 中异步地进行json数据的获取,主要在getJsonData(String url)方法中进行json数据的解析。
4.NewsBean.java为我们定义的用来存放数据的javabean
package com.example.admin.mylistviewdemo;
/**
* Created by admin on 2016/9/5.
*/
public class NewsBean {
public String newsIconUrl;
public String newsTitle;
public String newsContent;
}
5.我们自定义了一个类:ImageLoader,来进行图片的加载
package com.example.admin.mylistviewdemo;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.os.AsyncTask;
import android.util.Log;
import android.util.LruCache;
import android.widget.ImageView;
import android.widget.ListView;
import java.io.BufferedInputStream;
import java.io.IOException;
import java.net.HttpURLConnection;
import java.net.URL;
import java.util.HashSet;
import java.util.Set;
/**
* Created by admin on 2016/9/5.
*/
public class ImageLoader {
//创建cache
private LruCache<String, Bitmap> mLruCache;
//将传入的ListView赋给mListView,从而可以直接对列表项进行设置,比如只加载可见的列表项,或者设置bitmap等
private ListView mListView;
//创建set,用来存储我们的那些加载图片的线程,便于在需要的时候开启或者关闭相应的后台人任务
private Set<MyAsyncTask> mTasks;
//构造方法,传入我们需要加载进图片的listview,并在该构造函数中初始化缓存大小
//我们通过传如ListView,从而可以在需要的时候对ListView直接进行设置
public ImageLoader(ListView listView) {
mListView = listView;
mTasks = new HashSet<>();
//获得系统当前最大可用内存,这里取最大缓存的1/4来作为此应用的缓存大小
int maxMemory = (int) Runtime.getRuntime().maxMemory();
int cacheSize = maxMemory / 4;
mLruCache = new LruCache<String, Bitmap>(cacheSize) {
//覆写其sizeOf(String key, Bitmap value)方法,
// 返回值就是我们每次存入数据所需缓存空间的大小
@Override
protected int sizeOf(String key, Bitmap value) {
//return super.sizeOf(key, value);
//返回值为我们需要存入的bitmap的大小
return value.getByteCount();
}
};
}
//增加数据到缓存中
public void addBitmapToCache(String url, Bitmap bitmap) {
//如果缓存中已经没有的话,就存入缓存,否则略过
if (getBitmapFromCache(url) == null) {
mLruCache.put(url, bitmap);
}
}
//从缓存中读取数据
public Bitmap getBitmapFromCache(String url) {
return mLruCache.get(url);
}
public void cancelAllTasks() {
for (MyAsyncTask task : mTasks) {
//传入false表示只取消那些没有开始执行的线程,如果线程正在执行中则不取消,等待其执行完毕
task.cancel(false);
}
}
//根据传入的图片的Url去获取对应的图片bitmap
class MyAsyncTask extends AsyncTask<String, Void, Bitmap> {
private String mUrl;
// public MyAsyncTask(String url){
// mUrl = url;
// }
@Override
protected void onPostExecute(Bitmap bitmap) {
super.onPostExecute(bitmap);
ImageView imageView = (ImageView) mListView.findViewWithTag(mUrl);
if (imageView != null && bitmap != null) {
imageView.setImageBitmap(bitmap);
}
mTasks.remove(this);
}
@Override
protected Bitmap doInBackground(String... params) {
mUrl = params[0];
Bitmap bitmap = getBitmapFromUrl(mUrl);
if (bitmap != null) {
addBitmapToCache(mUrl, bitmap);
}
return bitmap;
}
}
public Bitmap getBitmapFromUrl(String url) {
BufferedInputStream bufferedInputStream = null;
try {
HttpURLConnection connection = (HttpURLConnection) new URL(url).openConnection();
bufferedInputStream = new BufferedInputStream(connection.getInputStream());
Bitmap bitmap = BitmapFactory.decodeStream(bufferedInputStream);
//及时关闭数据连接
connection.disconnect();
return bitmap;
} catch (IOException e) {
e.printStackTrace();
} finally {
try {
bufferedInputStream.close();
} catch (IOException e) {
Log.d("qc", "未能正常关闭流");
e.printStackTrace();
}
}
return null;
}
public void loadImages(int start, int end) {
String url;
Bitmap bitmap;
MyAsyncTask task;
ImageView imageView;
for (int i = start; i < end; i++) {
url = NewsAdapter.URLS[i];
bitmap = getBitmapFromCache(url);
if (bitmap == null) {
task = new MyAsyncTask();
task.execute(url);
mTasks.add(task);
} else {
imageView = (ImageView) mListView.findViewWithTag(url);
imageView.setImageBitmap(bitmap);
}
}
}
}
在该类中我们使用了缓存机制:LruCache,以下是LruCache的源码
/*
* Copyright (C) 2009 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.android.camera.gallery;
import java.lang.ref.ReferenceQueue;
import java.lang.ref.WeakReference;
import java.util.HashMap;
import java.util.LinkedHashMap;
import java.util.Map;
public class LruCache<K, V> {
private final HashMap<K, V> mLruMap;
private final HashMap<K, Entry<K, V>> mWeakMap =
new HashMap<K, Entry<K, V>>();
private ReferenceQueue<V> mQueue = new ReferenceQueue<V>();
@SuppressWarnings("serial")
public LruCache(final int capacity) {
mLruMap = new LinkedHashMap<K, V>(16, 0.75f, true) {
@Override
protected boolean removeEldestEntry(Map.Entry<K, V> eldest) {
return size() > capacity;
}
};
}
private static class Entry<K, V> extends WeakReference<V> {
K mKey;
public Entry(K key, V value, ReferenceQueue<V> queue) {
super(value, queue);
mKey = key;
}
}
@SuppressWarnings("unchecked")
private void cleanUpWeakMap() {
Entry<K, V> entry = (Entry<K, V>) mQueue.poll();
while (entry != null) {
mWeakMap.remove(entry.mKey);
entry = (Entry<K, V>) mQueue.poll();
}
}
public synchronized V put(K key, V value) {
cleanUpWeakMap();
mLruMap.put(key, value);
Entry<K, V> entry = mWeakMap.put(
key, new Entry<K, V>(key, value, mQueue));
return entry == null ? null : entry.get();
}
public synchronized V get(K key) {
cleanUpWeakMap();
V value = mLruMap.get(key);
if (value != null) return value;
Entry<K, V> entry = mWeakMap.get(key);
return entry == null ? null : entry.get();
}
public synchronized void clear() {
mLruMap.clear();
mWeakMap.clear();
mQueue = new ReferenceQueue<V>();
}
}
从源码中我们可以清楚的看到,LruCache的本质就是一个hashmap.在这里我们根据指定的tag来设置对应的bitmmap,这里bitmap和其tag(具体为图片的Url)关联了起来。
6.自定义一个adapter:NewsAdapter,来为listview提供数据
package com.example.admin.mylistviewdemo;
import android.content.Context;
import android.graphics.Bitmap;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.AbsListView;
import android.widget.BaseAdapter;
import android.widget.ImageView;
import android.widget.ListView;
import android.widget.TextView;
import java.util.List;
/**
* Created by admin on 2016/9/5.
*/
public class NewsAdapter extends BaseAdapter implements AbsListView.OnScrollListener {
private List<NewsBean> mNewsBeanList;
private Context mContext;
private ListView mListView;
private LayoutInflater mInflater;
private ImageLoader mImageLoader;
private boolean isJsutEnter;
private int mStart, mEnd;
public static String[] URLS;
/**
* 传入context对象,从而可以生成LayoutInflater来解析布局文件
* 根据传进来的mNewsBeanList来为listview提供数据
* 根据传进来的ListView,可以在需要的时候直接对列表项的数据进行修改,比如设置bitmap等
*/
public NewsAdapter(Context context, List<NewsBean> mNewsBeanList, ListView listView) {
mContext = context;
mInflater = LayoutInflater.from(mContext);
this.mNewsBeanList = mNewsBeanList;
mListView = listView;
mImageLoader = new ImageLoader(listView);
isJsutEnter = true;
URLS = new String[mNewsBeanList.size()];
for (int i = 0;i< URLS.length;i++){
URLS[i]= mNewsBeanList.get(i).newsIconUrl;
}
//为listView添加滚动监听器
mListView.setOnScrollListener(this);
}
//只有在滑动状态发生变化的时候才会回调该onScrollStateChanged方法,比如由滑动状态变为停止状态
@Override
public void onScrollStateChanged(AbsListView view, int scrollState) {
//当滑动停止的时候就加载可见项,否则不加载,停止所有执行加载任务的线程
if (scrollState == SCROLL_STATE_IDLE) {
mImageLoader.loadImages(mStart, mEnd);
} else{
mImageLoader.cancelAllTasks();
}
}
//每一次滚动都会回调该onScroll方法
@Override
public void onScroll(AbsListView view, int firstVisibleItem, int visibleItemCount, int totalItemCount) {
mStart = firstVisibleItem;
mEnd = firstVisibleItem + visibleItemCount;
//预加载第一页,进行visibleItemCount > 0判断是因为当列表项还没有加载出来的时候,
// 也就是当visibleItemCount为0的时候,onScroll方法就已经被回调了
if (isJsutEnter && visibleItemCount > 0) {
mImageLoader.loadImages(mStart, mEnd);
isJsutEnter = false;
}
}
@Override
public int getCount() {
return mNewsBeanList.size();
}
@Override
public Object getItem(int position) {
return mNewsBeanList.get(position);
}
@Override
public long getItemId(int position) {
return position;
}
@Override
public View getView(int position, View convertView, ViewGroup parent) {
ViewHolder viewHolder ;
if (convertView == null) {
convertView = mInflater.inflate(R.layout.item_layout, null);
viewHolder = new ViewHolder();
//从布局文件中取出对应控件的引用,并赋值给ViewHolder中的相应的控件
viewHolder.tvTitle = (TextView) convertView.findViewById(R.id.tv_title);
viewHolder.tvContent = (TextView) convertView.findViewById(R.id.tv_content);
viewHolder.ivIcon = (ImageView) convertView.findViewById(R.id.iv_icon);
//为convertView设置viewHolder标签
convertView.setTag(viewHolder);
} else {
//通过getTag()取出与convertView关联的viewHolder
viewHolder = (ViewHolder) convertView.getTag();
}
//设置viewHolder的tvTitle和tvContent,但是不能直接设置ivIcon,
// 因为在我们需要开启线程去获取图片,这样在滑动的时候会出现图片错位的问题
//因此我们需要为ivIcon设置标签(这里我们将bitmap的url设置为它的标签,从而将ivIcon与其对应的图片地址关联起来)
viewHolder.tvTitle.setText(mNewsBeanList.get(position).newsTitle);
viewHolder.tvContent.setText(mNewsBeanList.get(position).newsContent);
//为viewHolder的ivIcon设置tag
//该设置实际上是对每个ivIcon设置唯一标签,就是该ivIcon只能显示由newsIconUrl唯一标识的bitmap
String url = mNewsBeanList.get(position).newsIconUrl;
viewHolder.ivIcon.setTag(url);
//如果缓存中没有我们需要的bitmap,就显示默认图片,否则就将该bitmap设置为ivIcon显示的图片
//之所以可以这样做,是因为我们是从缓存中取bitmap,而不是从网络加载,不会差生错位。
//当到缓存中没有的时候,我们虽然加载了默认图片,看似不符合要求,
// 但是我们可以在其他地方改成我们需要的正确的图片就好了(在滑动监听器中获取我们需要的bitmap并修改)
Bitmap bitmap = mImageLoader.getBitmapFromCache(url);
if (bitmap == null) {
viewHolder.ivIcon.setImageResource(R.mipmap.ic_launcher);
} else {
viewHolder.ivIcon.setImageBitmap(bitmap);
}
return convertView;
}
class ViewHolder {
private ImageView ivIcon;
private TextView tvTitle;
private TextView tvContent;
}
}
这里我们通过自定义一个ViewHolder,并将其设置为convertView的Tag,从而实现重用,并且之后通过为ImageView设置Tag来避免了出现图片的错位,通过实现 AbsListView.OnScrollListener接口,我们在需要覆写的方法中实现了ListView的预加载,并且子滚动过程中不进行图片的获取,只有当滚动停止时才进行可见列表项的设置。
PS:表示CSDN的编辑功能真的太难用了,用一会就什么也不想写了,图片插入大小还要手动设置,不可以直接拉伸。。。还是觉得博客园的好用点