分页刷新的网络图片资源加载
- 引言:在我们的日常开发中,网络图片资源的加载十分常见。优美的图片结合我们强大的功能,才能在性能和感官上带给用户双重的美好体验。然而,若是将资源图片全部下载到本地,又会使我们的apk十分庞大,令人望而却步。那么今天,就一起来通过两个优秀的开源库,实现一下网络图片的加载吧。
1.开源库介绍
1.Picasso介绍
Picasso是由大名鼎鼎的Square公司开发的一款优秀的开源库,主要用于加载网络图片,是一款使用简单,功能强大的图片加载库。
Picasso有什么优势呢?主要有以下的几点:
[1] 提供传入url获取网络图片的方法,使用便捷。
[2] 采用LRU算法来完成图片加载的二级缓存,从而不必每次都从网络获取。
[3] 线程与图片的自动回收销毁。
[4] 自动对网络高清图进行压缩及像素的改变,从而适配对应的机型。
2.Picasso的使用
(1)添加包依赖1
2//图片加载依赖
compile 'com.squareup.picasso:picasso:2.5.2'
(2)使用1
2
3
4
5
6
7
8/**
* 参数说明
* 1.with(Context context):这个不用多说,全局上下文对象,一般都会用到。
* 2.load(String url):此方法用来传入我们已知的url地址,从而获取图片。
* 3.placeholder(int resId):此方法让我们在网络图片未返回时,显示一张本地默认图片。
* 4.into(ImageView target):用来将网络获取的图片填充到指定的ImagerView控件中。
*/
Picasso.with(mContext).load(mResultList.get(position).getUrl()).placeholder(R.mipmap.avatar).into(holder.imageView);
到了这一步,就已经大功告成了!是不是很简单,这就是它独特的魅力。下面我们来看看网络通信服务的开源库OkHttp。
3.OkHttp的介绍
OkHttp,又是Square公司的得意之作,是一款android中用于网络服务请求的轻量级框架,它替代了google原生的HttpUrlConnection,并支持了很多的功能。有以下的几方面优势:
[1] 支持HTTP/2 协议,允许连接到同一个主机地址的所有请求共享Socket。
[2] 路由节点管理,提升访问速度。
[3] GZip透明压缩减少传输的数据包大小,节省网络流量。
[4] 默认启用长连接,使用连接池管理,支持Cache(目前仅支持GET请求的缓存)。
在本次的网络请求中,我们只是利用它最简单的功能,即通过它去服务取到数据,并以流的形式返回给我们去处理。
4.OkHttp的使用
(1)添加包依赖1
2//网络请求
compile 'com.squareup.okhttp3:okhttp:3.4.1'
(2)使用1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24/**
* 该方法用来获取网络返回的Json
*/
public void sendRequestWithOkHttp() {
new Thread(new Runnable() {
public void run() {
try {
//构建OkhttpClient对象
OkHttpClient okHttpClient = new OkHttpClient();
//这里是我们的请求网址
String send_url = url + page_number;
//创建一个Request对象用来发起http请求()
Request request = new Request.Builder().url(send_url).build();
//数据接收(这里的接收可以看出请求了服务器一次,若要再次请求需要重新构建对象)
Response response = okHttpClient.newCall(request).execute();
String json = response.body().string();
dealWithJson(json);
} catch (Exception e) {
e.printStackTrace();
}
}
}).start();
}
到了这里,我们的依赖库就介绍完毕了,接下来进入编码阶段。
2.实现网络图片的加载
首先需要定义出我们的布局,使用RecyclerView在主界面list出获取到的网络图片资源:
<FrameLayout
android:id="@+id/container"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="@android:color/white">
<android.support.v7.widget.RecyclerView
android:id="@+id/img_list"
android:layout_width="match_parent"
android:layout_height="match_parent">
</android.support.v7.widget.RecyclerView>
</FrameLayout>
接着,定义子布局list_item.xml
<?xml version="1.0" encoding="utf-8"?>
//采用卡片布局实现图片圆角化
<android.support.v7.widget.CardView xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
xmlns:app="http://schemas.android.com/apk/res-auto"
//设置圆角
app:cardCornerRadius="2dp"
//设置Y轴阴影
app:cardElevation="2dp"
//padding设置
app:cardUseCompatPadding="true"
android:layout_height="wrap_content">
<ImageView
android:id="@+id/image"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:adjustViewBounds="true"
/>
</android.support.v7.widget.CardView>
RecyclerView的Adapter:
public class MyAdapter extends RecyclerView.Adapter<RecyclerView.ViewHolder> {
private Context mContext;
private List<Result> mResultList;
private static final String TAG = "MyAdapter";
public MyAdapter(Context context, List<Result> list) {
mContext = context;
mResultList = list;
}
public RecyclerView.ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
View view = LayoutInflater.from(mContext).inflate(R.layout.list_item,parent,false);
return new MyHolder(view);
}
public void onBindViewHolder(RecyclerView.ViewHolder holderBack, int position) {
MyHolder holder = (MyHolder)holderBack;
//使用Picasso来加载网络图片赋值给Adapter中对应控件
Picasso.with(mContext).load(mResultList.get(position).getUrl()).placeholder(R.mipmap.avatar).into(holder.imageView);
}
public int getItemCount() {
return mResultList.size();
}
//定义View
private class MyHolder extends RecyclerView.ViewHolder{
ImageView imageView;
MyHolder(View itemView) {
super(itemView);
imageView = itemView.findViewById(R.id.image);
}
}
我们定义好布局以及Adapter,接下来就是我们的逻辑代码实现了。代码如下:
/**
* 该方法用来获取网络返回的Json
*/
public void sendRequestWithOkHttp(final boolean isLoad) {
new Thread(new Runnable() {
public void run() {
try {
OkHttpClient okHttpClient = new OkHttpClient();
if(!isLoad) {
page_number = 1;
}
String send_url = url + page_number;
Request request = new Request.Builder().url(send_url).build();
Response response = okHttpClient.newCall(request).execute();
String json = response.body().string();
//子线程等待1s
Thread.sleep(1000);
dealWithJson(json);
} catch (Exception e) {
e.printStackTrace();
}
}
}).start();
}
/**
* 处理网络获取的Json
*
* @param json 获取的Json
*/
private void dealWithJson(String json) {
//利用Gson进行解析
Gson gson = new Gson();
Data data = gson.fromJson(json, Data.class);
//拿到网络数据
returnResults = data.getResults();
//主线程更新视图
runOnUiThread(new Runnable() {
public void run() {
//初始化适配器
if (myAdapter == null) {
myAdapter = new MyAdapter(MainActivity.this, results);
//设置适配器
mRecyclerView.setAdapter(myAdapter);
}
}
});
}
在这里,我们开启子线程,使用OkHttp去网络上请求并返回了资源Json,然后通过解析拿到对应的图片资源,封装给Adapter用RecyclerView进行显示。看看最终的实现效果:

3.添加动态上拉刷新和下拉加载更多的效果
1.下拉加载更多的实现思路
(1) 定义一个refresh_footer表示加载中的布局,让其当做RecyclerView的最后一项。
(2) 监听RecyclerView的滑动事件,如果滑动到数据的最后一项则显示定义的View,同时访问网络获取数据。
(3) 将数据重新封装好,填充Adapter动态更新,实现下拉加载。
2.实现过程:
首先需要定义一个表示加载中的布局,这里布局如下:
<?xml version="1.0" encoding="utf-8"?>
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="40dp">
<ProgressBar
style="?android:attr/progressBarStyle"
android:layout_gravity="center"
android:layout_width="30dp"
android:layout_height="30dp"/>
</FrameLayout>
接着我们需要修改Adapter,用来将我们的布局添加进去,那么第一步就是改变长度:
public int getItemCount() {
return mResultList.size()+1;
}
第二步,改变ViewType的类型,用以将我们定义的布局与子Item布局视图进行区分:
public int getItemViewType(int position) {
if (position < mResultList.size()) {
return super.getItemViewType(position);
} else {
return -1;
}
}
第三步,创建自定义视图的ViewHolder:
// 定义底部刷新View对应的ViewHolder
private class FooterHolder extends RecyclerView.ViewHolder {
FooterHolder(View view){
super(view);
}
}
最后一步,实现视图的加载工作,并且只有在不是尾部的时候才去绑定控件:
public RecyclerView.ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
// 如果是底部刷新View,则加载底部刷新View布局,并创建底部刷新View对应的ViewHolder
if (viewType == -1) {
View view = LayoutInflater.from(parent.getContext()).inflate(R.layout.refresh_footer, parent, false);
return new FooterHolder(view);
}
// 如果是其他类型的View,则按照正常流程创建普通的ViewHolder
else {
View view = LayoutInflater.from(mContext).inflate(R.layout.list_item,parent,false);
return new MyHolder(view);
}
public void onBindViewHolder(RecyclerView.ViewHolder holderBack, int position) {
if (!(holderBack instanceof FooterHolder)) {
MyHolder holder = (MyHolder) holderBack;
//使用Picasso来加载网络图片赋值给Adapter中对应控件
Picasso.with(mContext).load(mResultList.get(position).getUrl()).placeholder(R.mipmap.avatar).into(holder.imageView);
}
}
这样,当我们下拉到最后一项时,就实现了刷新View显示在我们list尾部的效果。接下来看主Activity怎么去实现:
//滑动监听
RecyclerView.OnScrollListener mOnScrollListener = new RecyclerView.OnScrollListener() {
//此变量用于判断我们是否已经加载过一遍了
private boolean mAlreadyRefreshed = false;
/*
* onScrollStateChanged()方法会在每次滑动状态发生改变时调用。
* 例如,由静止状态变为滑动状态,或者由滑动状态变为静止状态时,onScrollStateChanged()方法都会被调用。
*/
public void onScrollStateChanged(RecyclerView recyclerView, int newState) {
super.onScrollStateChanged(recyclerView, newState);
if (newState == RecyclerView.SCROLL_STATE_IDLE) {
//如果滑动停止且刷新过了,就恢复原态
if (mAlreadyRefreshed) {
mAlreadyRefreshed = false;
}
}
}
/*
* 此方法会在RecyclerView滑动时被调用
* 即使手指离开了屏幕,只要RecyclerView仍然在滑动onScrolled()就会被不断调用。
*/
public void onScrolled(RecyclerView recyclerView, int dx, int dy) {
super.onScrolled(recyclerView, dx, dy);
//如果在刷新或者刷新过了,则不再执行刷新
if (isBottomRefreshing() || mAlreadyRefreshed) {
return;
}
//否则执行刷新
if (isBottomViewVisible()) {
//改变页数
page_number++;
//执行刷新
sendRequestWithOkHttp(true);
mBottomRefreshing = true;
mAlreadyRefreshed = true;
}
}
};
//获取当前最后一个可见Item(即我们的底部View)在list中的位置
private int getLastVisibleItemPosition() {
RecyclerView.LayoutManager manager = mRecyclerView.getLayoutManager();
if (manager instanceof LinearLayoutManager) {
return ((LinearLayoutManager) manager).findLastVisibleItemPosition();
}
return -1;
}
//判断底部View是否可见
private boolean isBottomViewVisible() {
int lastVisibleItem = getLastVisibleItemPosition();
return lastVisibleItem != -1 && lastVisibleItem == myAdapter.getItemCount() - 1;
}
//是否正在刷新
public boolean isBottomRefreshing() {
return mBottomRefreshing;
}
//执行完刷新应该调用此方法
public void onBottomRefreshComplete() {
mBottomRefreshing = false;
}
以上就是主要的逻辑,我们来分析一下。
首先,我们将加载更多的功能放在了onScrolled
方法中,表示只要滑动过程中显示出来了我们的加载更多
视图,就去请求加载数据。那如何得知此视图已经被加载出来了呢?借用LinearLayoutManager的findLastVisibleItemPosition
去获取加载出来的最后一个视图的位置,接着去判断此位置是不是最后一个,是的话就返回true去请求网络加载视图。
其次,由于这两个监听方法在滑动过程中必然存在着多次执行的情况,我们为了避免资源的消耗,采用两个变量去控制。一是mBottomRefreshing
表示此时是否已经在刷新了(初始值为false),是的话就不再去请求刷新了。二是mAlreadyRefreshed
表示是否已经刷新过了,
如果刷新过了的话,就不再进行刷新了。
最后,RecyclerView的数据更新,当我们请求了数据后,让其实时更新UI,代码如下:
//拿到网络数据
returnResults = data.getResults();
//添加到集合中
Boolean add = results.addAll(returnResults);
if (add) {
//主线程更新视图
runOnUiThread(new Runnable() {
public void run() {
//初始化适配器
if (myAdapter == null) {
myAdapter = new MyAdapter(MainActivity.this, results);
//设置适配器
mRecyclerView.setAdapter(myAdapter);
} else {
//每次请求都是10张图
myAdapter.notifyItemRangeInserted(results.size(), 10);
}
//重置
onBottomRefreshComplete();
}
});
}
3.上拉刷新的实现
相对于下拉加载更多来说,上拉刷新就比较简单了,我们直接将RecyclerView包裹在SwipeRefreshLayout
控件中,通过监听其刷新事件,进行请求就行。
布局代码:
<android.support.v4.widget.SwipeRefreshLayout
android:id="@+id/refresh"
android:layout_width="match_parent"
android:layout_height="match_parent">
<FrameLayout
android:id="@+id/container"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="@android:color/white">
<android.support.v7.widget.RecyclerView
android:id="@+id/img_list"
android:layout_width="match_parent"
android:layout_height="match_parent">
</android.support.v7.widget.RecyclerView>
</FrameLayout>
</android.support.v4.widget.SwipeRefreshLayout>
逻辑处理:
refreshLayout.setOnRefreshListener(new SwipeRefreshLayout.OnRefreshListener() {
public void onRefresh() {
//清空缓存
results.clear();
//请求最新
sendRequestWithOkHttp(false);
}
});
好了,到了这里,我们已经将本次图片的加载操作完整的进行了一遍,让我们来看看最终的实现效果吧。
