通过学习MVP设计模式,想写一个小例子巩固一下。写个什么例子好呢?之前学习过慕课网的有关异步加载的视频,就把这个例子改编一下吧。
使用到的json数据网址是:http://www.imooc.com/api/teacher?type=4&num=30
效果图
项目目录图
思路图
- 根据当前滚动的状态变化异步加载图片
- 当滚动停止时加载当前可见的item对应的图片
- 当listview处于非停止状态时取消所有加载任务
- 第一次进入页面时由于listview的状态没有改变所以不加载图片,需要增加一个boolean类型isFirst标识,在onScroll方法中触发加载。
- 将加载的图片存到LruCache中,当加载的时候先从LruCache中获取,如果没有再加载。
V层
主布局文件activity_main
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context="com.sjb.bupt.asynctaskdemo.view.activity.MainActivity"
android:orientation="vertical">
<ListView
android:id="@+id/lv"
android:layout_width="match_parent"
android:layout_height="match_parent">
</ListView>
</LinearLayout>
item布局文件
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="horizontal" android:layout_width="match_parent"
android:layout_height="wrap_content">
<ImageView
android:id="@+id/iv_icon"
android:layout_width="70dp"
android:layout_height="70dp"
android:src="@mipmap/ic_launcher"/>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical"
android:paddingLeft="5dp"
android:gravity="center"
>
<TextView
android:id="@+id/tv_title"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:textColor="#333"
android:textSize="16sp"
android:maxLines="1"
android:text="标题"/>
<TextView
android:id="@+id/tv_content"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:textSize="12sp"
android:maxLines="3"
android:text="内容"/>
</LinearLayout>
</LinearLayout>
接口IMainActivityView
P层与V层通信需要通过此接口
package com.sjb.bupt.asynctaskdemo.view;
import android.graphics.Bitmap;
import android.widget.ImageView;
import com.sjb.bupt.asynctaskdemo.model.bean.NewsEntity;
import java.util.List;
/**
* Created by sjb on 2017/7/5.
*/
public interface IMainActivityView {
/**
* 显示listview的数据
* @param datas
*/
void showListView(List<NewsEntity> datas);
/**
* 获取listView数据失败是调用
*/
void loadFailed();
/**
* 显示异步加载的图片
* @param imageView
* @param bitmap
*/
void showAsyncTaskImage(ImageView imageView, Bitmap bitmap);
}
MainActivity
MainActivity实现接口IMainActivityView和接口 AbsListView.OnScrollListener来监听listview的滚动事件
package com.sjb.bupt.asynctaskdemo.view.activity;
import android.graphics.Bitmap;
import android.os.Bundle;
import android.support.v7.app.AppCompatActivity;
import android.widget.AbsListView;
import android.widget.ImageView;
import android.widget.ListView;
import android.widget.Toast;
import com.sjb.bupt.asynctaskdemo.R;
import com.sjb.bupt.asynctaskdemo.adapter.MyAdapter;
import com.sjb.bupt.asynctaskdemo.model.bean.NewsEntity;
import com.sjb.bupt.asynctaskdemo.presenter.ImageLoadPresenter;
import com.sjb.bupt.asynctaskdemo.presenter.LoadDataPresenter;
import com.sjb.bupt.asynctaskdemo.view.IMainActivityView;
import java.util.List;
public class MainActivity extends AppCompatActivity implements IMainActivityView, AbsListView.OnScrollListener {
private ListView lv;
private MyAdapter myAdapter;
private String url="http://www.imooc.com/api/teacher?type=4&num=30";
private LoadDataPresenter presenter;//加载数据的presenter
private ImageLoadPresenter mImageLoadPresenter;//加载图片的presenter
private int mVisibleStartItem,mVisibleEndItem;//需要加载图片listview中item的开始和结束
public static String[] URLS;//将数据中的图片URL存起来
private boolean isFirst=true;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
initViews();//初始化控件
initObjects();//初始化对象
initEvents();//初始化事件
loadDatas();//加载listView的数据
}
private void initEvents() {
lv.setOnScrollListener(this);
}
private void loadDatas() {
presenter.getDatas(url);
}
private void initObjects() {
presenter=new LoadDataPresenter(this);
mImageLoadPresenter = new ImageLoadPresenter(this,lv);
}
private void initViews() {
lv = (ListView) findViewById(R.id.lv);
}
@Override
public void showListView(List<NewsEntity> datas) {
URLS=new String[datas.size()];
for (int i=0;i<datas.size();i++) {
URLS[i]=datas.get(i).getPicSmall();
}
myAdapter=new MyAdapter(this,datas);
lv.setAdapter(myAdapter);
}
@Override
public void loadFailed() {
Toast.makeText(this,"数据加载失败",Toast.LENGTH_SHORT).show();
}
@Override
public void showAsyncTaskImage(ImageView imageView, Bitmap bitmap) {
imageView.setImageBitmap(bitmap);
}
@Override
public void onScrollStateChanged(AbsListView view, int scrollState) {
if (scrollState == SCROLL_STATE_IDLE) {
//加载图片
mImageLoadPresenter.showImages(mVisibleStartItem, mVisibleEndItem);
}else{
//停止加载
mImageLoadPresenter.cancelAllTask();
}
}
@Override
public void onScroll(AbsListView view, int firstVisibleItem, int visibleItemCount, int totalItemCount) {
mVisibleStartItem=firstVisibleItem;
mVisibleEndItem=firstVisibleItem+visibleItemCount;
if (isFirst&&visibleItemCount>0) {
mImageLoadPresenter.showImages(mVisibleStartItem,mVisibleEndItem);
isFirst=false;
}
}
}
adapter
listview的MyAdapter代码如下
package com.sjb.bupt.asynctaskdemo.adapter;
import android.content.Context;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.BaseAdapter;
import android.widget.ImageView;
import android.widget.TextView;
import com.sjb.bupt.asynctaskdemo.R;
import com.sjb.bupt.asynctaskdemo.model.bean.NewsEntity;
import java.util.List;
/**
* Created by sjb on 2017/7/5.
*/
public class MyAdapter extends BaseAdapter {
private List<NewsEntity> mDatas;
private Context mContex;
public MyAdapter(Context context, List<NewsEntity> datas){
this.mContex=context;
this.mDatas=datas;
}
@Override
public int getCount() {
return mDatas.size();
}
@Override
public Object getItem(int position) {
return mDatas.get(position);
}
@Override
public long getItemId(int position) {
return position;
}
@Override
public View getView(int position, View convertView, ViewGroup parent) {
if (convertView==null){
convertView = LayoutInflater.from(mContex).inflate(R.layout.listview_item, parent, false);
}
MyViewHolder holder= MyViewHolder.getViewHolder(convertView);
NewsEntity news = mDatas.get(position);
holder.tv_title.setText(news.getName());
holder.tv_content.setText(news.getDescription());
holder.iv_icon.setImageResource(R.mipmap.ic_launcher);
holder.iv_icon.setTag(news.getPicSmall());
return convertView;
}
static class MyViewHolder{
private ImageView iv_icon;
private TextView tv_title;
private TextView tv_content;
private MyViewHolder(View convertView){
iv_icon = (ImageView) convertView.findViewById(R.id.iv_icon);
tv_title = (TextView) convertView.findViewById(R.id.tv_title);
tv_content = (TextView) convertView.findViewById(R.id.tv_content);
}
public static MyViewHolder getViewHolder(View convertView){
MyViewHolder holder = (MyViewHolder) convertView.getTag();
if (holder == null) {
holder= new MyViewHolder(convertView);
convertView.setTag(holder);
}
return holder;
}
}
}
P层
加载数据的LoadDataPresenter
package com.sjb.bupt.asynctaskdemo.presenter;
import com.sjb.bupt.asynctaskdemo.model.GetNewsModelImpl;
import com.sjb.bupt.asynctaskdemo.model.IGetNewsModel;
import com.sjb.bupt.asynctaskdemo.model.bean.NewsEntity;
import com.sjb.bupt.asynctaskdemo.view.IMainActivityView;
import java.util.List;
/**
* Created by sjb on 2017/7/5.
*/
public class LoadDataPresenter {
private IGetNewsModel mIGetNewsModel;
private IMainActivityView mIMainActivityView;
public LoadDataPresenter(IMainActivityView iMainActivityView){
this.mIMainActivityView=iMainActivityView;
mIGetNewsModel=new GetNewsModelImpl();
}
public void getDatas(String url){
mIGetNewsModel.getNews(url, new IGetNewsModel.OnGetNewsListener() {
@Override
public void onSuccess(List<NewsEntity> newsEntities) {
mIMainActivityView.showListView(newsEntities);
}
@Override
public void onFailed() {
mIMainActivityView.loadFailed();
}
});
}
}
异步加载图片的ImageLoadPresenter
package com.sjb.bupt.asynctaskdemo.presenter;
import android.graphics.Bitmap;
import android.widget.ImageView;
import android.widget.ListView;
import com.sjb.bupt.asynctaskdemo.view.activity.MainActivity;
import com.sjb.bupt.asynctaskdemo.model.IImageLoadModel;
import com.sjb.bupt.asynctaskdemo.model.ImageLoadModelImpl;
import com.sjb.bupt.asynctaskdemo.view.IMainActivityView;
/**
* Created by sjb on 2017/7/6.
*/
public class ImageLoadPresenter {
private IMainActivityView mIMainActivityView;
private IImageLoadModel mIImageLoadModel;
private ListView mListView;
public ImageLoadPresenter(IMainActivityView iMainActivityView, ListView listView){
this.mIMainActivityView=iMainActivityView;
this.mListView=listView;
mIImageLoadModel=new ImageLoadModelImpl(mListView);
}
public void showImages(int start,int end){
for (int i=start;i<end;i++) {
mIImageLoadModel.loadImage(MainActivity.URLS[i], new IImageLoadModel.OnImageLoadListener() {
@Override
public void onSuccess(ImageView imageView, Bitmap bitmap) {
mIMainActivityView.showAsyncTaskImage(imageView,bitmap);
}
@Override
public void onFailed() {
}
});
}
}
public void cancelAllTask(){
mIImageLoadModel.cancelAllTask();
}
}
M层
根据json数据格式写了两个实体类
DatasEntity
package com.sjb.bupt.asynctaskdemo.model.bean;
import java.util.List;
/**
* Created by sjb on 2017/7/5.
*/
public class DatasEntity {
private String status;
private List<NewsEntity> data;
public String getStatus() {
return status;
}
public void setStatus(String status) {
this.status = status;
}
public List<NewsEntity> getData() {
return data;
}
public void setData(List<NewsEntity> data) {
this.data = data;
}
}
NewsEntity
package com.sjb.bupt.asynctaskdemo.model.bean;
/**
* Created by sjb on 2017/7/5.
*/
public class NewsEntity {
private int id;
private String name;
private String picSmall;
private String picBig;
private String description;
private int learner;
public int getId() {
return id;
}
public void setId(int id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getPicSmall() {
return picSmall;
}
public void setPicSmall(String picSmall) {
this.picSmall = picSmall;
}
public String getPicBig() {
return picBig;
}
public void setPicBig(String picBig) {
this.picBig = picBig;
}
public String getDescription() {
return description;
}
public void setDescription(String description) {
this.description = description;
}
public int getLearner() {
return learner;
}
public void setLearner(int learner) {
this.learner = learner;
}
}
获取数据的接口model
package com.sjb.bupt.asynctaskdemo.model;
import com.sjb.bupt.asynctaskdemo.model.bean.NewsEntity;
import java.util.List;
/**
* Created by sjb on 2017/7/5.
*/
public interface IGetNewsModel {
/**
* 获取listview的数据
* @param url
* @param listener
*/
void getNews(String url, GetNewsModelImpl.OnGetNewsListener listener);
/**
* 监听获取数据的回调接口
*/
interface OnGetNewsListener {
void onSuccess(List<NewsEntity> newsEntities);
void onFailed();
}
}
获取数据接口的实现类GetNewsModelImpl
package com.sjb.bupt.asynctaskdemo.model;
import android.os.AsyncTask;
import android.os.Handler;
import com.google.gson.Gson;
import com.sjb.bupt.asynctaskdemo.model.bean.DatasEntity;
import com.sjb.bupt.asynctaskdemo.model.bean.NewsEntity;
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;
/**
* Created by sjb on 2017/7/5.
*/
public class GetNewsModelImpl implements IGetNewsModel {
private OnGetNewsListener mListener;
private Handler myHandler = new Handler();
@Override
public void getNews(String url,OnGetNewsListener listener) {
this.mListener=listener;
new NewsAsyncTask().execute(url);
}
private List<NewsEntity> getJsonDatas(String url) {
List<NewsEntity> datas = new ArrayList<>();
try {
String jsonResult = readStream(new URL(url).openStream());
Gson gson=new Gson();
DatasEntity datasEntity = gson.fromJson(jsonResult, DatasEntity.class);
datas = datasEntity.getData();
} catch (Exception e) {
myHandler.post(new Runnable() {
@Override
public void run() {
mListener.onFailed();
}
});
e.printStackTrace();
}
return datas;
}
private String readStream(InputStream is) {
InputStreamReader isr;
String result = "";
try {
String line = "";
isr = new InputStreamReader(is, "utf-8");
BufferedReader br = new BufferedReader(isr);
while ((line = br.readLine()) != null) {
result += line;
}
} catch (UnsupportedEncodingException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}
return result;
}
class NewsAsyncTask extends AsyncTask<String, Void, List<NewsEntity>> {
@Override
protected List<NewsEntity> doInBackground(String... params) {
return getJsonDatas(params[0]);
}
@Override
protected void onPostExecute(List<NewsEntity> newsEntities) {
super.onPostExecute(newsEntities);
if (newsEntities != null) {
mListener.onSuccess(newsEntities);
}else{
mListener.onFailed();
}
}
}
}
加载图片的接口Model
package com.sjb.bupt.asynctaskdemo.model;
import android.graphics.Bitmap;
import android.widget.ImageView;
/**
* Created by sjb on 2017/7/6.
*/
public interface IImageLoadModel {
/**
* 通过url获取bitmap
* @param url
* @return
*/
Bitmap getBitmapFromUrl(String url);
/**
* 加载图片
* @param url
* @param listener
*/
void loadImage(String url,OnImageLoadListener listener);
/**
* 取消所有加载任务
*/
void cancelAllTask();
/**
* 图片加载回调接口
*/
interface OnImageLoadListener{
void onSuccess(ImageView imageView, Bitmap bitmap);
void onFailed();
}
}
加载图片接口model的实现类ImageLoadModelImpl
package com.sjb.bupt.asynctaskdemo.model;
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.IOException;
import java.io.InputStream;
import java.net.HttpURLConnection;
import java.net.URL;
import java.util.ArrayList;
import java.util.List;
import static android.content.ContentValues.TAG;
/**
* Created by sjb on 2017/7/5.
*/
public class ImageLoadModelImpl implements IImageLoadModel {
LruCache<String, Bitmap> mCache;
private OnImageLoadListener mListener;
private ListView mListView;
private List<ImageAsyncTask> tasks;
public ImageLoadModelImpl(ListView listView) {
this.mListView=listView;
tasks = new ArrayList<>();
final int maxMemory = (int) Runtime.getRuntime().maxMemory();
Log.d(TAG, "ImageLoadModelImpl: " + maxMemory);
mCache = new LruCache<String, Bitmap>(maxMemory / 4) {
@Override
protected int sizeOf(String key, Bitmap bitmap) {
return bitmap.getByteCount();
}
};
}
@Override
public void loadImage(String url, OnImageLoadListener listener) {
this.mListener = listener;
ImageView imageView=(ImageView) mListView.findViewWithTag(url);
Bitmap bitmap = getBitmapFromCache(url);
if (bitmap != null) {
mListener.onSuccess(imageView, bitmap);
} else {
ImageAsyncTask task = new ImageAsyncTask(imageView, url);
tasks.add(task);
task.execute(url);
}
}
@Override
public void cancelAllTask() {
if (tasks.size() != 0) {
for (ImageAsyncTask task : tasks) {
task.cancel(false);
}
}
}
private void addBitmapToCache(String key, Bitmap bitmap) {
if (getBitmapFromUrl(key) == null) {
mCache.put(key, bitmap);
}
}
private Bitmap getBitmapFromCache(String key) {
return mCache.get(key);
}
@Override
public Bitmap getBitmapFromUrl(String url) {
Bitmap bitmap = null;
HttpURLConnection connection = null;
InputStream is = null;
try {
URL mUrl = new URL(url);
connection = (HttpURLConnection) mUrl.openConnection();
is = connection.getInputStream();
bitmap = BitmapFactory.decodeStream(is);
} catch (Exception e) {
e.printStackTrace();
} finally {
try {
if (is != null) {
is.close();
}
} catch (IOException e) {
e.printStackTrace();
}
if (connection != null) {
connection.disconnect();
}
}
return bitmap;
}
private class ImageAsyncTask extends AsyncTask<String, Void, Bitmap> {
private String url;
private ImageView imageView;
public ImageAsyncTask(ImageView imageView,String url) {
this.url = url;
this.imageView = imageView;
}
@Override
protected Bitmap doInBackground(String... params) {
return getBitmapFromUrl(params[0]);
}
@Override
protected void onPostExecute(Bitmap bitmap) {
super.onPostExecute(bitmap);
if (imageView.getTag().equals(url)) {
//将图片存到缓存中
if (bitmap != null) {
addBitmapToCache(url, bitmap);
}
mListener.onSuccess(imageView, bitmap);
}
tasks.remove(this);
}
}
}
结束
如有错误望指出,相互交流,谢谢!